From 3ee3fed45eedad48dfbd3af48c316f78a76e1b8d Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 1 Aug 2022 09:09:37 +0800 Subject: [PATCH 001/163] This is where vChewing begins. --- McBopomofo.xcodeproj/project.pbxproj | 8 + Source/Data | 1 + Source/Engine/Gramambular/Bigram.h | 2 + .../Engine/Gramambular/BlockReadingBuilder.h | 2 +- Source/Engine/Gramambular/KeyValuePair.h | 1 + Source/Engine/Gramambular/Node.h | 19 ++ Source/InputMethodController.h | 4 + Source/InputMethodController.mm | 130 ++++------- Source/McBopomofo-Info.plist | 2 +- Source/OVInputSourceHelper.m | 1 - Source/UserOverrideModel.cpp | 219 ++++++++++++++++++ Source/UserOverrideModel.h | 81 +++++++ 12 files changed, 387 insertions(+), 83 deletions(-) create mode 160000 Source/Data create mode 100644 Source/UserOverrideModel.cpp create mode 100644 Source/UserOverrideModel.h diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 64f7b808c..2ad9cf5ee 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 6AD7CBC815FE555000691B5B /* data-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */; }; 6AE210B215FC63CC003659FE /* PlainBopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */; }; 6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */; }; + 6AE30A491F7F40B7008735BD /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6AE30A471F7F40B7008735BD /* UserOverrideModel.cpp */; }; 6AFF97F2253B299E007F1C49 /* OVNonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */; }; 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */; }; D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */; }; @@ -206,6 +207,8 @@ 6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-plain-bpmf.txt"; sourceTree = ""; }; 6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = PlainBopomofo.tiff; sourceTree = ""; }; 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "PlainBopomofo@2x.tiff"; sourceTree = ""; }; + 6AE30A471F7F40B7008735BD /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = ""; }; + 6AE30A481F7F40B7008735BD /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; 6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVNonModalAlertWindowController.h; sourceTree = ""; }; 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OVNonModalAlertWindowController.xib; sourceTree = ""; }; 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OVNonModalAlertWindowController.m; sourceTree = ""; }; @@ -284,6 +287,10 @@ 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */, 6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */, 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */, + 6A0D4ECD15FC0D6400ABF4B3 /* UpdateNotificationController.h */, + 6A0D4ECE15FC0D6400ABF4B3 /* UpdateNotificationController.m */, + 6AE30A471F7F40B7008735BD /* UserOverrideModel.cpp */, + 6AE30A481F7F40B7008735BD /* UserOverrideModel.h */, D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */, D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */, ); @@ -647,6 +654,7 @@ 6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */, 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */, 6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */, + 6AE30A491F7F40B7008735BD /* UserOverrideModel.cpp in Sources */, 6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */, 6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, diff --git a/Source/Data b/Source/Data new file mode 160000 index 000000000..30a5bc773 --- /dev/null +++ b/Source/Data @@ -0,0 +1 @@ +Subproject commit 30a5bc773a21d67c3f117eef5346d3b35433bbaa diff --git a/Source/Engine/Gramambular/Bigram.h b/Source/Engine/Gramambular/Bigram.h index 194ea755c..42ac9033b 100644 --- a/Source/Engine/Gramambular/Bigram.h +++ b/Source/Engine/Gramambular/Bigram.h @@ -28,6 +28,8 @@ #ifndef Bigram_h #define Bigram_h +#include + #include "KeyValuePair.h" namespace Formosa { diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index f6909b063..ed6fd1733 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -199,7 +199,7 @@ namespace Formosa { } } - const string BlockReadingBuilder::Join(vector::const_iterator begin, vector::const_iterator end, const string& separator) + inline const string BlockReadingBuilder::Join(vector::const_iterator begin, vector::const_iterator end, const string& separator) { string result; for (vector::const_iterator iter = begin ; iter != end ; ) { diff --git a/Source/Engine/Gramambular/KeyValuePair.h b/Source/Engine/Gramambular/KeyValuePair.h index ea6fd33d4..0abbb8911 100644 --- a/Source/Engine/Gramambular/KeyValuePair.h +++ b/Source/Engine/Gramambular/KeyValuePair.h @@ -28,6 +28,7 @@ #ifndef KeyValuePair_h #define KeyValuePair_h +#include #include namespace Formosa { diff --git a/Source/Engine/Gramambular/Node.h b/Source/Engine/Gramambular/Node.h index 89a748134..f265ebe91 100644 --- a/Source/Engine/Gramambular/Node.h +++ b/Source/Engine/Gramambular/Node.h @@ -46,11 +46,13 @@ namespace Formosa { bool isCandidateFixed() const; const vector& candidates() const; void selectCandidateAtIndex(size_t inIndex = 0, bool inFix = true); + void selectFloatingCandidateAtIndex(size_t index, double score); void resetCandidate(); const string& key() const; double score() const; const KeyValuePair currentKeyValue() const; + double highestUnigramScore() const; protected: const LanguageModel* m_LM; @@ -165,6 +167,16 @@ namespace Formosa { m_candidateFixed = inFix; m_score = 99; + } + + inline void Node::selectFloatingCandidateAtIndex(size_t index, double score) { + if (index >= m_unigrams.size()) { + m_selectedUnigramIndex = 0; + } else { + m_selectedUnigramIndex = index; + } + m_candidateFixed = false; + m_score = score; } inline void Node::resetCandidate() @@ -185,6 +197,13 @@ namespace Formosa { { return m_score; } + + inline double Node::highestUnigramScore() const { + if (m_unigrams.empty()) { + return 0.0; + } + return m_unigrams[0].score; + } inline const KeyValuePair Node::currentKeyValue() const { diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index 8741cbedb..71437a00f 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -37,6 +37,7 @@ #import "Mandarin.h" #import "Gramambular.h" #import "FastLM.h" +#import "UserOverrideModel.h" @interface McBopomofoInputMethodController : IMKInputController { @@ -53,6 +54,9 @@ // latest walked path (trellis) using the Viterbi algorithm std::vector _walkedNodes; + // user override model + McBopomofo::UserOverrideModel *_uom; + // the latest composing buffer that is updated to the foreground app NSMutableString *_composingBuffer; NSInteger _latestReadingCursor; diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 9dec8a06b..b599fdf09 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -76,9 +76,9 @@ static NSString *const kCandidateListTextSizeKey = @"CandidateListTextSize"; static NSString *const kSelectPhraseAfterCursorAsCandidatePreferenceKey = @"SelectPhraseAfterCursorAsCandidate"; static NSString *const kUseHorizontalCandidateListPreferenceKey = @"UseHorizontalCandidateList"; static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize"; -static NSString *const kDisableUserCandidateSelectionLearning = @"DisableUserCandidateSelectionLearning"; static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey"; static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey"; +static NSString *const kDisableUserCandidateSelectionLearning = @"DisableUserCandidateSelectionLearning"; // advanced (usually optional) settings static NSString *const kCandidateTextFontName = @"CandidateTextFontName"; @@ -118,6 +118,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot FastLM gLanguageModel; FastLM gLanguageModelPlainBopomofo; +static const int kUserOverrideModelCapacity = 500; +static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. +McBopomofo::UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); + // https://clang-analyzer.llvm.org/faq.html __attribute__((annotate("returns_localized_nsstring"))) static inline NSString *LocalizationNotNeeded(NSString *s) { @@ -132,10 +136,7 @@ static inline NSString *LocalizationNotNeeded(NSString *s) { - (void)collectCandidates; - (size_t)actualCandidateCursorIndex; -- (NSString *)neighborTrigramString; -- (void)_performDeferredSaveUserCandidatesDictionary; -- (void)saveUserCandidatesDictionary; - (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client; - (void)beep; @@ -152,6 +153,36 @@ public: } }; +static const double kEpsilon = 0.000001; + +static double FindHighestScore(const vector& nodes, double epsilon) { + double highestScore = 0.0; + for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { + double score = ni->node->highestUnigramScore(); + if (score > highestScore) { + highestScore = score; + } + } + return highestScore + epsilon; +} + +static void OverrideCandidate(const vector& nodes, const string& candidateValue, bool fixed, double floatingNodeOverrideScore) { + for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { + const vector& candidates = (*ni).node->candidates(); + for (size_t i = 0, c = candidates.size(); i < c; ++i) { + if (candidates[i].value == candidateValue) { + // found our node + if (fixed) { + const_cast((*ni).node)->selectCandidateAtIndex(i); + } else { + const_cast((*ni).node)->selectFloatingCandidateAtIndex(i, floatingNodeOverrideScore); + } + return; + } + } + } +} + @implementation McBopomofoInputMethodController - (void)dealloc { @@ -182,6 +213,7 @@ public: // create the lattice builder _languageModel = &gLanguageModel; _builder = new BlockReadingBuilder(_languageModel); + _uom = &gUserOverrideModel; // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -189,11 +221,6 @@ public: // create the composing buffer _composingBuffer = [[NSMutableString alloc] init]; - // populate the settings, by default, DISABLE user candidate learning - if (![[NSUserDefaults standardUserDefaults] objectForKey:kDisableUserCandidateSelectionLearning]) { - [[NSUserDefaults standardUserDefaults] setObject:(id)kCFBooleanTrue forKey:kDisableUserCandidateSelectionLearning]; - } - _inputMode = kBopomofoModeIdentifier; _chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; } @@ -693,15 +720,15 @@ public: // then walk the lattice [self popOverflowComposingTextAndWalk:client]; - // see if we need to override the selection if a learned one exists - if (![[NSUserDefaults standardUserDefaults] boolForKey:kDisableUserCandidateSelectionLearning]) { - NSString *trigram = [self neighborTrigramString]; - - // Lookup from the user dict to see if the trigram fit or not - NSString *overrideCandidateString = [gCandidateLearningDictionary objectForKey:trigram]; - if (overrideCandidateString) { - [self candidateSelected:(NSAttributedString *)overrideCandidateString]; - } + // get user override model suggestion + string overrideValue = + (_inputMode == kPlainBopomofoModeIdentifier) ? "" : + _uom->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + if (!overrideValue.empty()) { + size_t cursorIndex = [self actualCandidateCursorIndex]; + vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); + double highestScore = FindHighestScore(nodes, kEpsilon); + OverrideCandidate(nodes, overrideValue, false, highestScore); } // then update the text @@ -1280,61 +1307,6 @@ public: return cursorIndex; } -- (NSString *)neighborTrigramString -{ - // gather the "trigram" for user candidate selection learning - - NSMutableArray *termArray = [NSMutableArray array]; - - size_t cursorIndex = [self actualCandidateCursorIndex]; - vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); - - const Node* prev = 0; - const Node* current = 0; - const Node* next = 0; - - size_t wni = 0; - size_t wnc = _walkedNodes.size(); - size_t accuSpanningLength = 0; - for (wni = 0; wni < wnc; wni++) { - NodeAnchor& anchor = _walkedNodes[wni]; - if (!anchor.node) { - continue; - } - - accuSpanningLength += anchor.spanningLength; - if (accuSpanningLength >= cursorIndex) { - prev = current; - current = anchor.node; - break; - } - - current = anchor.node; - } - - if (wni + 1 < wnc) { - next = _walkedNodes[wni + 1].node; - } - - string term; - if (prev) { - term = prev->currentKeyValue().key; - [termArray addObject:[NSString stringWithUTF8String:term.c_str()]]; - } - - if (current) { - term = current->currentKeyValue().key; - [termArray addObject:[NSString stringWithUTF8String:term.c_str()]]; - } - - if (next) { - term = next->currentKeyValue().key; - [termArray addObject:[NSString stringWithUTF8String:term.c_str()]]; - } - - return [termArray componentsJoinedByString:@"-"]; -} - - (void)_performDeferredSaveUserCandidatesDictionary { BOOL __unused success = [gCandidateLearningDictionary writeToFile:gUserCandidatesDictionaryPath atomically:YES]; @@ -1495,17 +1467,15 @@ public: // candidate selected, override the node with selection string selectedValue = [[_candidates objectAtIndex:index] UTF8String]; + size_t cursorIndex = [self actualCandidateCursorIndex]; - if (![[NSUserDefaults standardUserDefaults] boolForKey:kDisableUserCandidateSelectionLearning]) { - NSString *trigram = [self neighborTrigramString]; - NSString *selectedNSString = [NSString stringWithUTF8String:selectedValue.c_str()]; - [gCandidateLearningDictionary setObject:selectedNSString forKey:trigram]; - [self saveUserCandidatesDictionary]; + if (_inputMode != kPlainBopomofoModeIdentifier) { + _uom->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); } - - size_t cursorIndex = [self actualCandidateCursorIndex]; _builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue); + vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); + OverrideCandidate(nodes, selectedValue, true, 0.0); [_candidates removeAllObjects]; [self walk]; diff --git a/Source/McBopomofo-Info.plist b/Source/McBopomofo-Info.plist index 199cc0077..782d58b8a 100644 --- a/Source/McBopomofo-Info.plist +++ b/Source/McBopomofo-Info.plist @@ -143,4 +143,4 @@ tsInputMethodIconFileKey Bopomofo.tiff - + \ No newline at end of file diff --git a/Source/OVInputSourceHelper.m b/Source/OVInputSourceHelper.m index b7638a34f..6735545a4 100644 --- a/Source/OVInputSourceHelper.m +++ b/Source/OVInputSourceHelper.m @@ -32,7 +32,6 @@ { CFArrayRef list = TISCreateInputSourceList(NULL, true); return (__bridge NSArray *)list; -// return [NSMakeCollectable(list) autorelease]; } + (TISInputSourceRef)inputSourceForProperty:(CFStringRef)inPropertyKey stringValue:(NSString *)inValue diff --git a/Source/UserOverrideModel.cpp b/Source/UserOverrideModel.cpp new file mode 100644 index 000000000..8b2df5220 --- /dev/null +++ b/Source/UserOverrideModel.cpp @@ -0,0 +1,219 @@ +// +// UserOverrideModel.cpp +// +// Copyright (c) 2017 The McBopomofo Project. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#include "UserOverrideModel.h" + +#include +#include +#include + +using namespace McBopomofo; + +// About 20 generations. +static const double DecayThreshould = 1.0 / 1048576.0; + +static double Score(size_t eventCount, + size_t totalCount, + double eventTimestamp, + double timestamp, + double lambda); +static bool IsEndingPunctuation(const string& value); +static string WalkedNodesToKey(const std::vector& walkedNodes, + size_t cursorIndex); + +UserOverrideModel::UserOverrideModel(size_t capacity, double decayConstant) + : m_capacity(capacity) { + assert(m_capacity > 0); + m_decayExponent = log(0.5) / decayConstant; +} + +void UserOverrideModel::observe(const std::vector& walkedNodes, + size_t cursorIndex, + const string& candidate, + double timestamp) { + string key = WalkedNodesToKey(walkedNodes, cursorIndex); + auto mapIter = m_lruMap.find(key); + if (mapIter == m_lruMap.end()) { + auto keyValuePair = KeyObservationPair(key, Observation()); + Observation& observation = keyValuePair.second; + observation.update(candidate, timestamp); + + m_lruList.push_front(keyValuePair); + auto listIter = m_lruList.begin(); + auto lruKeyValue = std::pair::iterator>(key, listIter); + m_lruMap.insert(lruKeyValue); + + if (m_lruList.size() > m_capacity) { + auto lastKeyValuePair = m_lruList.end(); + --lastKeyValuePair; + m_lruMap.erase(lastKeyValuePair->first); + m_lruList.pop_back(); + } + } else { + auto listIter = mapIter->second; + m_lruList.splice(m_lruList.begin(), m_lruList, listIter); + + auto& keyValuePair = *listIter; + Observation& observation = keyValuePair.second; + observation.update(candidate, timestamp); + } +} + +string UserOverrideModel::suggest(const std::vector& walkedNodes, + size_t cursorIndex, + double timestamp) { + string key = WalkedNodesToKey(walkedNodes, cursorIndex); + auto mapIter = m_lruMap.find(key); + if (mapIter == m_lruMap.end()) { + return string(); + } + + auto listIter = mapIter->second; + auto& keyValuePair = *listIter; + const Observation& observation = keyValuePair.second; + + string candidate; + double score = 0.0; + for (auto i = observation.overrides.begin(); + i != observation.overrides.end(); + ++i) { + const Override& o = i->second; + double overrideScore = Score(o.count, + observation.count, + o.timestamp, + timestamp, + m_decayExponent); + if (overrideScore == 0.0) { + continue; + } + + if (overrideScore > score) { + candidate = i->first; + score = overrideScore; + } + } + return candidate; +} + +void UserOverrideModel::Observation::update(const string& candidate, + double timestamp) { + count++; + auto& o = overrides[candidate]; + o.timestamp = timestamp; + o.count++; +} + +static double Score(size_t eventCount, + size_t totalCount, + double eventTimestamp, + double timestamp, + double lambda) { + double decay = exp((timestamp - eventTimestamp) * lambda); + if (decay < DecayThreshould) { + return 0.0; + } + + double prob = (double)eventCount / (double)totalCount; + return prob * decay; +} + +static bool IsEndingPunctuation(const string& value) { + return value == "," || value == "。" || value== "!" || value == "?" || + value == "」" || value == "』" || value== "”" || value == "”"; +} +static string WalkedNodesToKey(const std::vector& walkedNodes, + size_t cursorIndex) { + std::stringstream s; + std::vector n; + size_t ll = 0; + for (std::vector::const_iterator i = walkedNodes.begin(); + i != walkedNodes.end(); + ++i) { + const auto& nn = *i; + n.push_back(nn); + ll += nn.spanningLength; + if (ll >= cursorIndex) { + break; + } + } + + std::vector::const_reverse_iterator r = n.rbegin(); + + if (r == n.rend()) { + return ""; + } + + string current = (*r).node->currentKeyValue().key; + ++r; + + s.clear(); + s.str(std::string()); + if (r != n.rend()) { + string value = (*r).node->currentKeyValue().value; + if (IsEndingPunctuation(value)) { + s << "()"; + r = n.rend(); + } else { + s << "(" + << (*r).node->currentKeyValue().key + << "," + << value + << ")"; + ++r; + } + } else { + s << "()"; + } + string prev = s.str(); + + s.clear(); + s.str(std::string()); + if (r != n.rend()) { + string value = (*r).node->currentKeyValue().value; + if (IsEndingPunctuation(value)) { + s << "()"; + r = n.rend(); + } else { + s << "(" + << (*r).node->currentKeyValue().key + << "," + << value + << ")"; + ++r; + } + } else { + s << "()"; + } + string anterior = s.str(); + + s.clear(); + s.str(std::string()); + s << "(" << anterior << "," << prev << "," << current << ")"; + + return s.str(); +} diff --git a/Source/UserOverrideModel.h b/Source/UserOverrideModel.h new file mode 100644 index 000000000..0b981923a --- /dev/null +++ b/Source/UserOverrideModel.h @@ -0,0 +1,81 @@ +// +// UserOverrideModel.h +// +// Copyright (c) 2017 The McBopomofo Project. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#ifndef USEROVERRIDEMODEL_H +#define USEROVERRIDEMODEL_H + +#include +#include +#include + +#include "Gramambular.h" + +namespace McBopomofo { + +using namespace Formosa::Gramambular; + +class UserOverrideModel { +public: + UserOverrideModel(size_t capacity, double decayConstant); + + void observe(const std::vector& walkedNodes, + size_t cursorIndex, + const string& candidate, + double timestamp); + + string suggest(const std::vector& walkedNodes, + size_t cursorIndex, + double timestamp); + +private: + struct Override { + size_t count; + double timestamp; + + Override() : count(0), timestamp(0.0) {} + }; + + struct Observation { + size_t count; + std::map overrides; + + Observation() : count(0) {} + void update(const string& candidate, double timestamp); + }; + + typedef std::pair KeyObservationPair; + + size_t m_capacity; + double m_decayExponent; + std::list m_lruList; + std::map::iterator> m_lruMap; +}; + +}; // namespace McBopomofo + +#endif + -- Gitee From 67fdecf807a638ef7fd000270c0f92369770c2cd Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 1 Aug 2022 09:19:47 +0800 Subject: [PATCH 002/163] Misc Tweaks + Add Git Submodule --- .../continuous-integration-workflow.yml | 11 ++--- .gitmodules | 3 ++ Makefile | 41 +++++++++++++++++++ McBopomofo.xcodeproj/project.pbxproj | 13 +++--- README.md | 14 +++++++ Source/McBopomofo-Info.plist | 2 +- 6 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 .gitmodules create mode 100644 Makefile create mode 100644 README.md diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 5c094c400..94b4415c7 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -6,15 +6,10 @@ jobs: name: Build runs-on: macOS-latest env: - DEVELOPER_DIR: /Applications/Xcode_12.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_13.2.app/Contents/Developer steps: - uses: actions/checkout@v1 - name: Clean - run: xcodebuild -scheme McBopomofo -configuration Release clean - - name: Clean - run: xcodebuild -scheme McBopomofoInstaller -configuration Release clean - - name: Build - run: xcodebuild -scheme McBopomofo -configuration Release build + run: make clean - name: Build - run: xcodebuild -scheme McBopomofoInstaller -configuration Release build - + run: git pull --all && git submodule sync; make update; make diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..df4cdd172 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Source/Data"] + path = Source/Data + url = https://gitee.com/vchewing/libvchewing-data diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..0e889293a --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ ++.PHONY: all + +all: release +install: install-release +update: + @git restore Source/Data/ + git submodule update --init --recursive --remote --force + +ifdef ARCHS +BUILD_SETTINGS += ARCHS="$(ARCHS)" +BUILD_SETTINGS += ONLY_ACTIVE_ARCH=NO +endif + +release: + xcodebuild -project McBopomofo.xcodeproj -scheme McBopomofoInstaller -configuration Release $(BUILD_SETTINGS) build + +debug: + xcodebuild -project McBopomofo.xcodeproj -scheme McBopomofoInstaller -configuration Debug $(BUILD_SETTINGS) build + +DSTROOT = /Library/Input Methods +VC_APP_ROOT = $(DSTROOT)/McBopomofo.app + +.PHONY: permission-check install-debug install-release + +permission-check: + [ -w "$(DSTROOT)" ] && [ -w "$(VC_APP_ROOT)" ] || sudo chown -R ${USER} "$(DSTROOT)" + +install-debug: permission-check + rm -rf "$(VC_APP_ROOT)" + open Build/Products/Debug/McBopomofoInstaller.app + +install-release: permission-check + rm -rf "$(VC_APP_ROOT)" + open Build/Products/Release/McBopomofoInstaller.app + +.PHONY: clean + +clean: + xcodebuild -scheme McBopomofoInstaller -configuration Debug $(BUILD_SETTINGS) clean + xcodebuild -scheme McBopomofoInstaller -configuration Release $(BUILD_SETTINGS) clean + make clean --file=./Source/Data/Makefile || true diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 2ad9cf5ee..b6f697479 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */; }; D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */; }; D48550A325EBE689006A204C /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = D48550A225EBE689006A204C /* OpenCC */; }; + /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -190,7 +191,7 @@ 6A38BBFB15FC117A00A8A51F /* phrase.occ */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = phrase.occ; sourceTree = ""; }; 6A38BBFC15FC117A00A8A51F /* PhraseFreq.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PhraseFreq.txt; sourceTree = ""; }; 6A38BBFD15FC117A00A8A51F /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = ""; }; - 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/InputMethodKit.framework; sourceTree = DEVELOPER_DIR; }; + 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/InputMethodKit.framework; sourceTree = DEVELOPER_DIR; }; 6ACA41CB15FC1D7500935EF6 /* McBopomofoInstaller.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = McBopomofoInstaller.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6ACA41E815FC1D9000935EF6 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Source/Installer/AppDelegate.h; sourceTree = SOURCE_ROOT; }; 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Source/Installer/AppDelegate.m; sourceTree = SOURCE_ROOT; }; @@ -287,8 +288,6 @@ 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */, 6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */, 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */, - 6A0D4ECD15FC0D6400ABF4B3 /* UpdateNotificationController.h */, - 6A0D4ECE15FC0D6400ABF4B3 /* UpdateNotificationController.m */, 6AE30A471F7F40B7008735BD /* UserOverrideModel.cpp */, 6AE30A481F7F40B7008735BD /* UserOverrideModel.h */, D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */, @@ -570,7 +569,7 @@ ); mainGroup = 6A0D4E9215FC0CFA00ABF4B3; packageReferences = ( - D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */, + D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyLibreCC" */, ); productRefGroup = 6A0D4EA315FC0D2D00ABF4B3 /* Products */; projectDirPath = ""; @@ -1133,9 +1132,9 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */ = { + D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyLibreCC" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ddddxxx/SwiftyOpenCC.git"; + repositoryURL = "https://dev.azure.com/ShikiSuen/vChewing/_git/SwiftyLibreCC"; requirement = { kind = revision; revision = 1d8105a0f7199c90af722bff62728050c858e777; @@ -1146,7 +1145,7 @@ /* Begin XCSwiftPackageProductDependency section */ D48550A225EBE689006A204C /* OpenCC */ = { isa = XCSwiftPackageProductDependency; - package = D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyOpenCC" */; + package = D48550A125EBE689006A204C /* XCRemoteSwiftPackageReference "SwiftyLibreCC" */; productName = OpenCC; }; /* End XCSwiftPackageProductDependency section */ diff --git a/README.md b/README.md new file mode 100644 index 000000000..359a83857 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# OpenVanilla McBopomofo 小麥注音輸入法 + +## 開發流程 + +用 Xcode 開啟 `McBopomofo.xcodeproj`,選 "McBopomofo Installer" target,build 完之後直接執行該安裝程式,就可以安裝小麥注音。 + +第一次安裝完,日後程式碼或詞庫有任何修改,只要重複上述流程,再次安裝小麥注音即可。 + +要注意的是 macOS 可能會限制同一次 login session 能 kill 同一個輸入法 process 的次數(安裝程式透過 kill input method process 來讓新版的輸入法生效)。如果安裝若干次後,發現程式修改的結果並沒有出現,或甚至輸入法已無法再選用,只要登出目前帳號再重新登入即可。 + +## 軟體授權 + +本專案採用 MIT License 釋出,使用者可自由使用、散播本軟體,惟散播時必須完整保留版權聲明及軟體授權([詳全文](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。 + diff --git a/Source/McBopomofo-Info.plist b/Source/McBopomofo-Info.plist index 782d58b8a..199cc0077 100644 --- a/Source/McBopomofo-Info.plist +++ b/Source/McBopomofo-Info.plist @@ -143,4 +143,4 @@ tsInputMethodIconFileKey Bopomofo.tiff - \ No newline at end of file + -- Gitee From 44f62f25dcdcccc16661f2f434394de56cc1f6eb Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 1 Jan 2022 01:06:35 +0800 Subject: [PATCH 003/163] Update Data -- Gitee From efdf7772bbf708564841f4a0318e22cc5292e339 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 17 Dec 2021 16:04:16 +0800 Subject: [PATCH 004/163] Rebranding to vChewing, requiring macOS 10.12 --- .../continuous-integration-workflow.yml | 3 +- .gitignore | 1 + LICENSE | 12 + LICENSE-CHS.txt | 12 + LICENSE-CHT.txt | 12 + LICENSE.txt | 20 +- Makefile | 14 +- README.markdown | 14 - README.md | 31 +- Source/AppDelegate.h | 2 +- Source/AppDelegate.m | 6 +- Source/Base.lproj/MainMenu.xib | 8 +- .../VTHorizontalCandidateController.m | 6 +- .../CandidateUI/VTHorizontalCandidateView.m | 12 +- .../VTVerticalCandidateController.m | 32 +- .../VTVerticalCandidateTableView.m | 2 +- .../CandidateUI/VTVerticalKeyLabelStripView.m | 8 +- Source/Images/Bopomofo.tiff | Bin 4430 -> 4926 bytes Source/Images/Bopomofo@2x.tiff | Bin 7502 -> 5496 bytes .../AlertIcon.imageset/128X128.png | Bin 2910 -> 8072 bytes .../AlertIcon.imageset/192x192.png | Bin 4096 -> 16915 bytes .../AlertIcon.imageset/64X64.png | Bin 1568 -> 4460 bytes .../AppIcon.appiconset/1024X1024.png | Bin 24519 -> 53724 bytes .../AppIcon.appiconset/128X128.png | Bin 2910 -> 8072 bytes .../AppIcon.appiconset/16X16.png | Bin 495 -> 1750 bytes .../AppIcon.appiconset/256X256.png | Bin 5348 -> 15555 bytes .../AppIcon.appiconset/32X32.png | Bin 869 -> 2580 bytes .../AppIcon.appiconset/512X512.png | Bin 11070 -> 30285 bytes .../AppIcon.appiconset/64X64.png | Bin 1568 -> 4460 bytes Source/Images/PlainBopomofo.tiff | Bin 4430 -> 4958 bytes Source/Images/PlainBopomofo@2x.tiff | Bin 7502 -> 5720 bytes Source/InputMethodController.h | 6 +- Source/InputMethodController.mm | 158 ++++++---- Source/Installer/AppDelegate.h | 2 +- Source/Installer/AppDelegate.m | 14 +- Source/Installer/ArchiveUtil.h | 2 +- Source/Installer/ArchiveUtil.m | 2 +- Source/Installer/Base.lproj/MainMenu.xib | 14 +- Source/Installer/en.lproj/InfoPlist.strings | 2 +- Source/Installer/en.lproj/License.rtf | 2 +- Source/Installer/en.lproj/Localizable.strings | 4 +- Source/Installer/main.m | 2 +- .../Installer/zh-Hans.lproj/InfoPlist.strings | 5 + Source/Installer/zh-Hans.lproj/License.rtf | 42 +++ .../zh-Hans.lproj/Localizable.strings | 39 +++ Source/Installer/zh-Hans.lproj/MainMenu.xib | 209 +++++++++++++ .../Installer/zh-Hant.lproj/InfoPlist.strings | 2 +- Source/Installer/zh-Hant.lproj/License.rtf | 36 +-- .../zh-Hant.lproj/Localizable.strings | 4 +- Source/Installer/zh-Hant.lproj/MainMenu.xib | 50 +-- Source/OpenCCBridge.swift | 2 +- Source/PreferencesWindowController.h | 2 +- Source/PreferencesWindowController.m | 2 +- Source/README | 17 +- Source/UpdateNotificationController.h | 2 +- Source/UpdateNotificationController.m | 2 +- Source/UserOverrideModel.cpp | 4 +- Source/UserOverrideModel.h | 6 +- Source/en.lproj/InfoPlist.strings | 8 +- Source/en.lproj/Localizable.strings | 8 +- Source/main.m | 4 +- ...ng-Header.h => vChewing-Bridging-Header.h} | 0 ...opomofo-Info.plist => vChewing-Info.plist} | 31 +- ...opomofo-Prefix.pch => vChewing-Prefix.pch} | 2 +- Source/zh-Hans.lproj/InfoPlist.strings | 5 + Source/zh-Hans.lproj/Localizable.strings | 52 +++ Source/zh-Hans.lproj/MainMenu.xib | 295 ++++++++++++++++++ Source/zh-Hans.lproj/preferences.xib | 237 ++++++++++++++ Source/zh-Hant.lproj/InfoPlist.strings | 8 +- Source/zh-Hant.lproj/Localizable.strings | 8 +- Source/zh-Hant.lproj/MainMenu.xib | 8 +- Source/zh-Hant.lproj/preferences.xib | 20 +- .../project.pbxproj | 106 ++++--- .../xcshareddata/xcschemes/vChewing.xcscheme | 18 +- .../xcschemes/vChewingInstaller.xcscheme | 18 +- 75 files changed, 1309 insertions(+), 346 deletions(-) create mode 100644 LICENSE create mode 100644 LICENSE-CHS.txt create mode 100644 LICENSE-CHT.txt delete mode 100644 README.markdown create mode 100644 Source/Installer/zh-Hans.lproj/InfoPlist.strings create mode 100644 Source/Installer/zh-Hans.lproj/License.rtf create mode 100644 Source/Installer/zh-Hans.lproj/Localizable.strings create mode 100644 Source/Installer/zh-Hans.lproj/MainMenu.xib rename Source/{McBopomofo-Bridging-Header.h => vChewing-Bridging-Header.h} (100%) rename Source/{McBopomofo-Info.plist => vChewing-Info.plist} (84%) rename Source/{McBopomofo-Prefix.pch => vChewing-Prefix.pch} (38%) create mode 100644 Source/zh-Hans.lproj/InfoPlist.strings create mode 100644 Source/zh-Hans.lproj/Localizable.strings create mode 100644 Source/zh-Hans.lproj/MainMenu.xib create mode 100644 Source/zh-Hans.lproj/preferences.xib rename {McBopomofo.xcodeproj => vChewing.xcodeproj}/project.pbxproj (93%) rename McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofo.xcscheme => vChewing.xcodeproj/xcshareddata/xcschemes/vChewing.xcscheme (83%) rename McBopomofo.xcodeproj/xcshareddata/xcschemes/McBopomofoInstaller.xcscheme => vChewing.xcodeproj/xcshareddata/xcschemes/vChewingInstaller.xcscheme (82%) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 94b4415c7..6d616b4b5 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -6,7 +6,8 @@ jobs: name: Build runs-on: macOS-latest env: - DEVELOPER_DIR: /Applications/Xcode_13.2.app/Contents/Developer + GIT_SSL_NO_VERIFY: true + DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer steps: - uses: actions/checkout@v1 - name: Clean diff --git a/.gitignore b/.gitignore index 3160b584c..5bebbc3c3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ Credits.rtf # that can be built by make -C Source/Data/bin/C_Version # C_count.occ.exe .idea +Source/Data/* \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..33c1907dc --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +MIT License + +Copyright (c) 2021 Mengjuei Hsieh et al. + +Shiki Suen, the maintainer of the vChewing project, does not own any rights of the programming parts of it. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/LICENSE-CHS.txt b/LICENSE-CHS.txt new file mode 100644 index 000000000..0644f3916 --- /dev/null +++ b/LICENSE-CHS.txt @@ -0,0 +1,12 @@ +MIT License +麻理许可协议 + +著作权利所有 © 2011-2021 Mengjuei Hsieh 等。 + +威注音输入法维护人孙志贵对该产品的程序部分不享有任何所有权。 + +软件的著作权利人依此麻理授权条款,将其对于软件的著作权利授权释出,只要使用者践履以下二项麻理授权条款叙明的义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用的权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面的应用,而散布程式之人、更可将上述权利传递予其后收受程式的后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款的义务性规定,则其对程式亦享有与前手运用范围相同的同一权利。 + +散布此一软件程序者,须将本条款其上的「著作权声明」及以下的「免责声明」,内嵌于软件程序及其重制作品的实体之中。 + +因麻理软件程序的授权模式乃是无偿提供,是以在现行法律的架构下可以主张合理的免除担保责任。麻理软件的著作权人或任何的后续散布者,对于其所散布的麻理软件程序皆不负任何形式上实质上的担保责任,明示亦或隐喻、商业利用性亦或特定目的使用性,这些均不在保障之列。利用麻理软件程序的所有风险均由使用者自行担负。假如所使用的麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要的服务支出。麻理软件程序的著作权人不负任何形式上实质上的担保责任,无论任何一般的、特殊的、偶发的、因果关系式的损害,或是麻理软件程序的不适用性,均须由使用者自行负担。 diff --git a/LICENSE-CHT.txt b/LICENSE-CHT.txt new file mode 100644 index 000000000..1c648da3f --- /dev/null +++ b/LICENSE-CHT.txt @@ -0,0 +1,12 @@ +MIT License +麻理授權條款 + +著作權利所有 © 2011-2021 Mengjuei Hsieh 等。 + +威注音輸入法維護人孫志貴對該產品的程式部分不享有任何所有權。 + +軟體的著作權利人依此麻理授權條款,將其對於軟體的著作權利授權釋出,只要使用者踐履以下二項麻理授權條款敘明的義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用的權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面的應用,而散布程式之人、更可將上述權利傳遞予其後收受程式的後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款的義務性規定,則其對程式亦享有與前手運用範圍相同的同一權利。 + +散布此一軟體程式者,須將本條款其上的「著作權聲明」及以下的「免責聲明」,內嵌於軟體程式及其重製作品的實體之中。 + +因麻理軟體程式的授權模式乃是無償提供,是以在現行法律的架構下可以主張合理的免除擔保責任。麻理軟體的著作權人或任何的後續散布者,對於其所散布的麻理軟體程式皆不負任何形式上實質上的擔保責任,明示亦或隱喻、商業利用性亦或特定目的使用性,這些均不在保障之列。利用麻理軟體程式的所有風險均由使用者自行擔負。假如所使用的麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要的服務支出。麻理軟體程式的著作權人不負任何形式上實質上的擔保責任,無論任何一般的、特殊的、偶發的、因果關係式的損害,或是麻理軟體程式的不適用性,均須由使用者自行負擔。 diff --git a/LICENSE.txt b/LICENSE.txt index 44849223f..375cec4ef 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -2,20 +2,10 @@ MIT License Copyright (c) 2011-2021 Mengjuei Hsieh et al. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Shiki Suen, the maintainer of the vChewing project, does not own any rights of the programming parts of it. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile index 0e889293a..319c64419 100644 --- a/Makefile +++ b/Makefile @@ -12,13 +12,13 @@ BUILD_SETTINGS += ONLY_ACTIVE_ARCH=NO endif release: - xcodebuild -project McBopomofo.xcodeproj -scheme McBopomofoInstaller -configuration Release $(BUILD_SETTINGS) build + xcodebuild -project vChewing.xcodeproj -scheme vChewingInstaller -configuration Release $(BUILD_SETTINGS) build debug: - xcodebuild -project McBopomofo.xcodeproj -scheme McBopomofoInstaller -configuration Debug $(BUILD_SETTINGS) build + xcodebuild -project vChewing.xcodeproj -scheme vChewingInstaller -configuration Debug $(BUILD_SETTINGS) build DSTROOT = /Library/Input Methods -VC_APP_ROOT = $(DSTROOT)/McBopomofo.app +VC_APP_ROOT = $(DSTROOT)/vChewing.app .PHONY: permission-check install-debug install-release @@ -27,15 +27,15 @@ permission-check: install-debug: permission-check rm -rf "$(VC_APP_ROOT)" - open Build/Products/Debug/McBopomofoInstaller.app + open Build/Products/Debug/vChewingInstaller.app install-release: permission-check rm -rf "$(VC_APP_ROOT)" - open Build/Products/Release/McBopomofoInstaller.app + open Build/Products/Release/vChewingInstaller.app .PHONY: clean clean: - xcodebuild -scheme McBopomofoInstaller -configuration Debug $(BUILD_SETTINGS) clean - xcodebuild -scheme McBopomofoInstaller -configuration Release $(BUILD_SETTINGS) clean + xcodebuild -scheme vChewingInstaller -configuration Debug $(BUILD_SETTINGS) clean + xcodebuild -scheme vChewingInstaller -configuration Release $(BUILD_SETTINGS) clean make clean --file=./Source/Data/Makefile || true diff --git a/README.markdown b/README.markdown deleted file mode 100644 index 359a83857..000000000 --- a/README.markdown +++ /dev/null @@ -1,14 +0,0 @@ -# OpenVanilla McBopomofo 小麥注音輸入法 - -## 開發流程 - -用 Xcode 開啟 `McBopomofo.xcodeproj`,選 "McBopomofo Installer" target,build 完之後直接執行該安裝程式,就可以安裝小麥注音。 - -第一次安裝完,日後程式碼或詞庫有任何修改,只要重複上述流程,再次安裝小麥注音即可。 - -要注意的是 macOS 可能會限制同一次 login session 能 kill 同一個輸入法 process 的次數(安裝程式透過 kill input method process 來讓新版的輸入法生效)。如果安裝若干次後,發現程式修改的結果並沒有出現,或甚至輸入法已無法再選用,只要登出目前帳號再重新登入即可。 - -## 軟體授權 - -本專案採用 MIT License 釋出,使用者可自由使用、散播本軟體,惟散播時必須完整保留版權聲明及軟體授權([詳全文](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。 - diff --git a/README.md b/README.md index 359a83857..54434bc97 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,31 @@ -# OpenVanilla McBopomofo 小麥注音輸入法 +# vChewing 威注音输入法 -## 開發流程 +威注音输入法由小麦注音分支而来(且词库内已经移除任何可以妨碍该输入法在世界上任何地方传播的内容),是原生简体中文注音输入法:相比中州韵(鼠须管)而言,威注音能够做到真正的大千声韵并击。 -用 Xcode 開啟 `McBopomofo.xcodeproj`,選 "McBopomofo Installer" target,build 完之後直接執行該安裝程式,就可以安裝小麥注音。 +威注音分支专案及威注音词库由孙志贵(Shiki Suen)维护。小麦注音官方原始仓库內的词库的内容均与孙志贵无关。 -第一次安裝完,日後程式碼或詞庫有任何修改,只要重複上述流程,再次安裝小麥注音即可。 +## 建置流程 -要注意的是 macOS 可能會限制同一次 login session 能 kill 同一個輸入法 process 的次數(安裝程式透過 kill input method process 來讓新版的輸入法生效)。如果安裝若干次後,發現程式修改的結果並沒有出現,或甚至輸入法已無法再選用,只要登出目前帳號再重新登入即可。 +系统需求:至少 macOS 10.12 Sierra。 -## 軟體授權 +安装 Xcode 之后,请先配置 Xcode 允许其直接构建在专案所在的资料夹下的 build 资料夹内。步骤: +``` +「Xcode」->「Preferences...」->「Locations」; +「File」->「Project/WorkspaceSettings...」->「Advanced」; +选「Custom」->「Relative to Workspace」即可。不选的话,make 的过程会出错。 +``` +在终端机内定位到威注音的克隆本地专案的本地仓库的目录之后,执行 `make update` 以获取最新词库,在成功之后执行 `make` 即可组建。再执行 `make install` 可以触发威注音的安装程式。 -本專案採用 MIT License 釋出,使用者可自由使用、散播本軟體,惟散播時必須完整保留版權聲明及軟體授權([詳全文](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。 +第一次安装完,日后程式码或词库有任何修改,只要重复上述流程,再次安装威注音即可。 +要注意的是 macOS 可能会限制同一次 login session 能终结同一个输入法的执行进程的次数(安装程式透过 kill input method process 来让新版的输入法生效)。如果安装若干次后,发现程式修改的结果并没有出现、或甚至输入法已无法再选用,只需要登出目前的 macOS 系统帐号、再重新登入即可。 + +补记: 该输入法是在 2021 年 11 月初「28ae7deb4092f067539cff600397292e66a5dd56」这一版小麦注音建置的基础上完成的。因为在清洗词库的时候清洗了全部的 git commit 历史,所以无法自动从小麦注音官方仓库上游继承任何改动,只能手动同步任何在此之后的程式修正。最近一次同步參照是「f7a24862c4e1733a2264b56e434d1a449325d769」。除此以外,还引入了 MJHsieh 制作(却尚未正式给小麦注音实装)的「临时记忆最近的部分选字词」的功能(该记忆有自己的忘却衰减曲线,且记忆的词汇会在每次重新开机时自动忘却。)。 + +## 应用授权 + +小麦注音引擎程式版权:© 2011-2021 OpenVanilla 专案团队(Mengjuei Hsieh 等人)。 + +威注音词库由孙志贵维护,亦以 MIT 授权释出。 + +本专案采用 MIT License 释出,使用者可自由使用、散播本软体,惟散播时必须完整保留版权声明及软体授权([详全文 LICENSE.txt](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。 diff --git a/Source/AppDelegate.h b/Source/AppDelegate.h index 96ee6e0c0..66d758bf7 100644 --- a/Source/AppDelegate.h +++ b/Source/AppDelegate.h @@ -1,7 +1,7 @@ // // AppDelegate.h // -// Copyright (c) 2011 The McBopomofo Project. +// Copyright (c) 2021 The vChewing Project. // // Contributors: // Mengjuei Hsieh (@mjhsieh) diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index cc6b896ce..eb7dc4962 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -1,7 +1,7 @@ // // AppDelegate.m // -// Copyright (c) 2011 The McBopomofo Project. +// Copyright (c) 2021 The vChewing Project. // // Contributors: // Mengjuei Hsieh (@mjhsieh) @@ -156,7 +156,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0; - (void)showNoUpdateAvailableAlert { - [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Check for Update Completed", nil) content:NSLocalizedString(@"You are already using the latest version of McBopomofo.", nil) confirmButtonTitle:NSLocalizedString(@"OK", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; + [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Check for Update Completed", nil) content:NSLocalizedString(@"You are already using the latest version of vChewing.", nil) confirmButtonTitle:NSLocalizedString(@"OK", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection @@ -244,7 +244,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0; } } - NSString *content = [NSString stringWithFormat:NSLocalizedString(@"You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@", nil), [infoDict objectForKey:@"CFBundleShortVersionString"], currentVersion, [plist objectForKey:@"CFBundleShortVersionString"], remoteVersion, versionDescription]; + NSString *content = [NSString stringWithFormat:NSLocalizedString(@"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", nil), [infoDict objectForKey:@"CFBundleShortVersionString"], currentVersion, [plist objectForKey:@"CFBundleShortVersionString"], remoteVersion, versionDescription]; [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"New Version Available", nil) content:content confirmButtonTitle:NSLocalizedString(@"Visit Website", nil) cancelButtonTitle:NSLocalizedString(@"Not Now", nil) cancelAsDefault:NO delegate:self]; } diff --git a/Source/Base.lproj/MainMenu.xib b/Source/Base.lproj/MainMenu.xib index 5f7bcf12f..4c79f2b95 100644 --- a/Source/Base.lproj/MainMenu.xib +++ b/Source/Base.lproj/MainMenu.xib @@ -14,10 +14,10 @@ - - + + - + @@ -32,7 +32,7 @@ - + diff --git a/Source/CandidateUI/VTHorizontalCandidateController.m b/Source/CandidateUI/VTHorizontalCandidateController.m index 4cc8795bc..d3246dbe1 100644 --- a/Source/CandidateUI/VTHorizontalCandidateController.m +++ b/Source/CandidateUI/VTHorizontalCandidateController.m @@ -57,7 +57,7 @@ self = [self initWithWindow:panel]; if (self) { contentRect.origin = NSMakePoint(0.0, 0.0); - _candidateView = [[VTHorizontalCandidateView alloc] initWithFrame:contentRect]; + _candidateView = [[VTHorizontalCandidateView alloc] initWithFrame:contentRect]; _candidateView.target = self; _candidateView.action = @selector(candidateViewMouseDidClick:); [[panel contentView] addSubview:_candidateView]; @@ -151,7 +151,7 @@ - (void)setSelectedCandidateIndex:(NSUInteger)newIndex { - NSUInteger keyLabelCount = [_keyLabels count]; + NSUInteger keyLabelCount = [_keyLabels count]; if (newIndex < [_delegate candidateCountForController:self]) { _currentPage = newIndex / keyLabelCount; _candidateView.highlightedIndex = newIndex % keyLabelCount; @@ -175,7 +175,7 @@ NSMutableArray *candidates = [NSMutableArray array]; NSUInteger count = [_delegate candidateCountForController:self]; - NSUInteger keyLabelCount = [_keyLabels count]; + NSUInteger keyLabelCount = [_keyLabels count]; for (NSUInteger index = _currentPage * keyLabelCount, j = 0; index < count && j < keyLabelCount; index++, j++) { [candidates addObject:[_delegate candidateController:self candidateAtIndex:index]]; } diff --git a/Source/CandidateUI/VTHorizontalCandidateView.m b/Source/CandidateUI/VTHorizontalCandidateView.m index d24122f50..765d0fc1b 100644 --- a/Source/CandidateUI/VTHorizontalCandidateView.m +++ b/Source/CandidateUI/VTHorizontalCandidateView.m @@ -96,7 +96,7 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } - (NSSize)sizeForView { NSSize result = NSMakeSize(0.0, 0.0); - if ([_elementWidths count]) { + if ([_elementWidths count]) { for (NSNumber *w in _elementWidths) { result.width += [w doubleValue]; } @@ -128,7 +128,7 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } [NSBezierPath strokeLineFromPoint:NSMakePoint(bounds.size.width, 0.0) toPoint:NSMakePoint(bounds.size.width, bounds.size.height)]; NSUInteger count = [_elementWidths count]; - CGFloat accuWidth = 0.0; + CGFloat accuWidth = 0.0; for (NSUInteger index = 0; index < count; index++) { NSDictionary *activeCandidateAttr = _candidateAttrDict; @@ -154,7 +154,7 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } } else { [backgroundColor setFill]; - } + } [NSBezierPath fillRect:candidateRect]; [[_displayedCandidates objectAtIndex:index] drawInRect:candidateRect withAttributes:activeCandidateAttr]; @@ -172,7 +172,7 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } return result; } - NSUInteger count = [_elementWidths count]; + NSUInteger count = [_elementWidths count]; CGFloat accuWidth = 0.0; for (NSUInteger index = 0; index < count; index++) { CGFloat currentWidth = [[_elementWidths objectAtIndex:index] doubleValue]; @@ -182,7 +182,7 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } break; } - accuWidth += currentWidth + 1.0; + accuWidth += currentWidth + 1.0; } return result; @@ -217,7 +217,7 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } # pragma clang diagnostic push # pragma clang diagnostic ignored "-Warc-performSelector-leaks" if (triggerAction && _target && _action) { - [_target performSelector:_action withObject:self]; + [_target performSelector:_action withObject:self]; } # pragma clang diagnostic pop } diff --git a/Source/CandidateUI/VTVerticalCandidateController.m b/Source/CandidateUI/VTVerticalCandidateController.m index 7150d45b3..68c340b5d 100644 --- a/Source/CandidateUI/VTVerticalCandidateController.m +++ b/Source/CandidateUI/VTVerticalCandidateController.m @@ -81,7 +81,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; scrollViewRect.origin.x = stripRect.size.width; scrollViewRect.size.width -= stripRect.size.width; - _scrollView = [[NSScrollView alloc] initWithFrame:scrollViewRect]; + _scrollView = [[NSScrollView alloc] initWithFrame:scrollViewRect]; // >=10.7 only, elastic scroll causes some drawing issues with visible scroller, so we disable it if ([_scrollView respondsToSelector:@selector(setVerticalScrollElasticity:)]) { @@ -155,7 +155,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; - (BOOL)highlightPreviousCandidate { - return [self moveSelectionByOne:NO]; + return [self moveSelectionByOne:NO]; } - (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index @@ -224,13 +224,13 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; { NSString *candidate = @""; - // rendering can occur when the delegate is already gone or data goes stale; in that case we ignore it + // rendering can occur when the delegate is already gone or data goes stale; in that case we ignore it if (row < [_delegate candidateCountForController:self]) { candidate = [_delegate candidateController:self candidateAtIndex:row]; } - NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:candidate attributes:[NSDictionary dictionaryWithObjectsAndKeys:_candidateFont, NSFontAttributeName, _candidateTextParagraphStyle, NSParagraphStyleAttributeName, nil]]; + NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:candidate attributes:[NSDictionary dictionaryWithObjectsAndKeys:_candidateFont, NSFontAttributeName, _candidateTextParagraphStyle, NSParagraphStyleAttributeName, nil]]; // we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead @@ -244,7 +244,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; // keep track of the highlighted index in the key label strip NSUInteger count = [_keyLabels count]; - NSInteger selectedRow = [_tableView selectedRow]; + NSInteger selectedRow = [_tableView selectedRow]; if (selectedRow != -1) { // cast this into signed integer to make our life easier NSInteger newHilightIndex; @@ -255,7 +255,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; else { NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin]; - newHilightIndex = selectedRow - firstVisibleRow; + newHilightIndex = selectedRow - firstVisibleRow; if (newHilightIndex < -1) { newHilightIndex = -1; } @@ -263,7 +263,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; if (newHilightIndex != _keyLabelStripView.highlightedIndex && newHilightIndex >= 0) { _keyLabelStripView.highlightedIndex = newHilightIndex; - [_keyLabelStripView setNeedsDisplay:YES]; + [_keyLabelStripView setNeedsDisplay:YES]; } } @@ -273,8 +273,8 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; - (void)tableViewSelectionDidChange:(NSNotification *)aNotification { NSInteger selectedRow = [_tableView selectedRow]; - if (selectedRow != -1) { - // keep track of the highlighted index in the key label strip + if (selectedRow != -1) { + // keep track of the highlighted index in the key label strip NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin]; _keyLabelStripView.highlightedIndex = selectedRow - firstVisibleRow; [_keyLabelStripView setNeedsDisplay:YES]; @@ -285,7 +285,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; if ([_scrollView hasVerticalScroller]) { [[_scrollView verticalScroller] setNeedsDisplay]; } - } + } } - (void)rowDoubleClicked:(id)sender @@ -331,8 +331,8 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; } } - self.selectedCandidateIndex = newIndex; - return YES; + self.selectedCandidateIndex = newIndex; + return YES; } - (BOOL)moveSelectionByOne:(BOOL)forward @@ -360,7 +360,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; newIndex--; } - self.selectedCandidateIndex = newIndex; + self.selectedCandidateIndex = newIndex; return YES; } @@ -393,7 +393,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; [_scrollView setHasVerticalScroller:YES]; NSScroller *verticalScroller = [_scrollView verticalScroller]; - [verticalScroller setControlSize:controlSize]; + [verticalScroller setControlSize:controlSize]; [verticalScroller setScrollerStyle:NSScrollerStyleLegacy]; scrollerWidth = [NSScroller scrollerWidthForControlSize:controlSize scrollerStyle:NSScrollerStyleLegacy]; } @@ -415,7 +415,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; maxKeyLabelWidth = max(rect.size.width, maxKeyLabelWidth); } - CGFloat rowSpacing = [_tableView intercellSpacing].height; + CGFloat rowSpacing = [_tableView intercellSpacing].height; CGFloat stripWidth = ceil(maxKeyLabelWidth * 1.20); CGFloat tableViewStartWidth = ceil(_maxCandidateAttrStringWidth + scrollerWidth);; CGFloat windowWidth = stripWidth + 1.0 + tableViewStartWidth; @@ -425,7 +425,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; NSPoint topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height); frameRect.size = NSMakeSize(windowWidth, windowHeight); - frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height); + frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height); [_keyLabelStripView setFrame:NSMakeRect(0.0, 0.0, stripWidth, windowHeight)]; [_scrollView setFrame:NSMakeRect(stripWidth + 1.0, 0.0, tableViewStartWidth, windowHeight)]; diff --git a/Source/CandidateUI/VTVerticalCandidateTableView.m b/Source/CandidateUI/VTVerticalCandidateTableView.m index bf326ac59..990f134da 100644 --- a/Source/CandidateUI/VTVerticalCandidateTableView.m +++ b/Source/CandidateUI/VTVerticalCandidateTableView.m @@ -30,7 +30,7 @@ @implementation VTVerticalCandidateTableView - (NSRect)adjustScroll:(NSRect)newVisible { - NSRect scrollRect = newVisible; + NSRect scrollRect = newVisible; CGFloat rowHeightPlusSpacing = [self rowHeight] + [self intercellSpacing].height; scrollRect.origin.y = (NSInteger)(scrollRect.origin.y / rowHeightPlusSpacing) * rowHeightPlusSpacing; return scrollRect; diff --git a/Source/CandidateUI/VTVerticalKeyLabelStripView.m b/Source/CandidateUI/VTVerticalKeyLabelStripView.m index 1a7d2a19b..1770d9f8f 100644 --- a/Source/CandidateUI/VTVerticalKeyLabelStripView.m +++ b/Source/CandidateUI/VTVerticalKeyLabelStripView.m @@ -58,14 +58,14 @@ { NSRect bounds = [self bounds]; [[NSColor whiteColor] setFill]; - [NSBezierPath fillRect:bounds]; + [NSBezierPath fillRect:bounds]; - NSUInteger count = [_keyLabels count]; + NSUInteger count = [_keyLabels count]; if (!count) { return; } - CGFloat cellHeight = bounds.size.height / count; + CGFloat cellHeight = bounds.size.height / count; NSColor *black = [NSColor blackColor]; NSColor *darkGray = [NSColor colorWithDeviceWhite:0.7 alpha:1.0]; NSColor *lightGray = [NSColor colorWithDeviceWhite:0.8 alpha:1.0]; @@ -99,6 +99,6 @@ NSString *text = [_keyLabels objectAtIndex:index]; [text drawInRect:textRect withAttributes:textAttr]; - } + } } @end diff --git a/Source/Images/Bopomofo.tiff b/Source/Images/Bopomofo.tiff index a1c29ed1e1980c6ecab09eed3f39456c9eba20e0..b802c702e99b65be7d4b43f27daa579d23c945f2 100644 GIT binary patch delta 1641 zcmZWp4NMbf7=FLI{#gnXWC~dEtjM3z_FBQxP;B@c8jw0!g2ruBDL+bEYzvgh?3FRV zpE(W0;9t0oC7>p=)5V}7B052*3^g+ZXI2p>PUle3s3C6G3Nqd0zTEr0_q@;h-Sge^ z^@%$p>{(e51Axc6071-<(-^RrCt`+>#;0Ot4sw~CRFR}g9#64>VldAUr>|%(IXv5TECifiJpG-$MHN$nPw1vsmW24Gd%WfA~wAWq@R&W`~FSMh%jI9KY z^Trj7PJU!ha6^npV7mPpyTEG`VhL`A%XlCgfvnaI5z`bs0e!Y~cb*XJoU`@Iz-Qjo zW&2$keE+t?JtM&9oI_*#9MkzShh{~`We{lPd5(dD3IO7mt?zS-SO$ky4*126^ zn-e5wove1yC(@|AHM$TY(}fh`d*r|jYJtlVmhERv%XKhf&q zuMW32H@1fd^ILm4c(o?3cgv;T6Bn!HAH^xA4HQfGB=5BI zZu-mAht9EzX7|oD(R1>Gh5Uo}yCdz8Ovgwz&MIt!(Aq;yLnMzzLzTx;GKO-Qzdwgf z@zbo1n}si5RUPQ!5kb73C_a}3*GkZ@SDr*%qBTW=Q~SBq528AJ6Up!+%NFTBf%vPh zP;=p)?lJ^T$u;=xvw^K>*Vkd&>9V`!&Vx}`pPekKKOD6=Z14T+I^2K90Huki z2trsNQPr`|Su&fy5b3yQe>o~*?Ha8Rg|B9OIkSP!n&)!~;fyFAW3n5RtobOt;Zv4G zgRerN!1ktrbrQ{b8+Rko1e|d)ikgTSHbi>8J{^EHh|~=~vgVT4unv#k(Qg3k&@WE` zm*HE;ynj?z{MP@xI*TVS$N=CxZx~{Q+qK0ueThM5HJJ&m*+j@C3IGvW4fw42ABvbp z7+@kS33#4EgCyngB5Vox$jCS{PQE~o1w&LQQ6dDUA}3pUd6Y15ZTx7MDs-)72iV8`E zLSix(O38$T1gT6emCIupM69LKXw}(bjh1;H7VqWdN!44{nTt!U#U>-+;nl4*m08td z@dV}#a$=!|QnlDV(aC6$cvmf1XEI1_x>6}AkxAcDGi8a)SE^FXdXG{$CR0h$|AkAG zzK@yeI#sSu)mimP9yw!4GS)7S&zU@g89h~upi?Ywpzo1nF`DMZ)=LFnxC8vV_UdbK*!}BMA%>$VUGGDYF zM{wIlG=MF?bMFC|1~~+V4<5e&W`oq=iYk!dAWMo`7l18BRtpk?g-Sgx1MrE1eAY92 z15V5F=>>%Z$RnVT0IGw;KRyjGIhYSps%C%Y+S}FBUXGB8djVBh{$XOafH2 zh!NHrSr;N|wOb25IHL6xsv;^@R_oRWDk7yKx>{vdbhX_HMeRB5oS8HCzw`ayz4yE4 z{CB>S4F?;OlOY&@aRX=;I-mgHMLmQK1v>Rcs0Xvr-r3X|2%M10q*56Xj^R>S7|cOQ!d2x0=jlL#C!M&ELsGb2)wO{16ffwlAS$!73t&fBo* zf6F>@i|5FBZvAX#_MN8obaFX&Y^`UH(bCb&ga_t)@%!y#s^1xM*Az2^PUkr`RMJFei|4;MAs;Qw`I) zRX1FS?_LAUbI$^OnTywZ9;^Ik=p3sJNm%fnXeogpW1><88;WwXO8tT(=!jh9%xNme zx`ov{mu}seZ{*vN@+HxqYW5ZZ)6~EE-0ttKvo@Odfd{SfNbaJ#A|zoEFs}~}hg|{g z2J~WICnMnNoXg`Y$YBsNkqFRkVZj8u*@$Ca$!0B`TusPyE<5bxR zjOa6?+q*8N+!#21pt17qwRCVVi?Dzoac@qDTlp}o7W~u&e8y8=CBMR>XOZ95Lp+G7 zQQ44yTTS!XtqV=|bEO}RyAdUr*MqRa*O9-*)`yPN?rr} zX>t%%|474-J4w7CACCKX4I2kC>!yd<^M-16TpgPRVe1r#FkQ-6P~!fz!pmw#O?!@? zgl|m0pieTb{Wwbjbr{2FgPKrv-igD9l-+i8Qo3Szp@;V-JNItm$5y_ zpDlWP>4t0a4&Muz?li0t(l-UKGvxYU2s+!lbNv@Jsr7hn$d$oQxA(v3+(c^!)HfYC zr;+61tlLk|X@=V>o1c=OwRXLnC1)dnopV@S?Z6Bg5CDW4#f?jIZx6rH(z(tw_{URu zx@fB8RUetkR+ZryOa)|Br8Vun`sDIN5q+v^JV&8}|q zhm9|N`_#^8Pvav+2Xj?wk@jd8@Zu^X0Vxcm&0*IvtoHPfhev7lCHpY-9-w9E#3i+E zYcFWbWXq(MpJZT{m53hiLM+%^XXpU8*iSB-6>>&pS1Ye9leg}$dU}@hd~n{-au1u( z1=aijaxcc;o&;z2Aec*R$apY9loMFMBd#=i|8uuPvtwWTCa75`xjsU5&4qU6a)$SR zcj0*mCUXi}eUV6-&a}DENPCKfl<{0&dWhYL!b=(Vr+Zd%t^^JC$IM# zlXP`VT5seT`G5F3lEj-1^q}@@A}wUxdFyhjX|Z&SL$`eSZc8jBg9hy z_(&$ud?qIHsYukB$wy;=iuQ}75Jk21&%`Gxr^y%ICzYYR$b zhG?NOSC^xbQMSV^5n>|{{jzA`{Nz+DURSKb!o|247sjwLBZfs4=Pr?@CwwrvpISu= z)p~uQOd?saVug6cRI#o&Pa=(sjFjMngdju|LR7M{K(9223QGJPEF;$lPlBpsaj~XQ zuhA7?4qj!B?qhwlP&kVD4LO=DVo(-Jq+(q1mYUiYMO7(F zrs~O6>Xivfy(-3`h)60G5#bq*F+5d9gi?ygC`lxLz`hGV!V_Ptawwjm(;2lff9$|- z1n)rFHzmHwW8{w-Q6)OB$cyYl9OLEbL<{PYvwn|w3#&$;KG|J+mVFnD_dT>$80@Zk_qFkQej zjqH`Oi0S{HC**)!LS6(%qD$xpwR}&`k!(Xw=mYd+r18;^jaJM*wU*Jy_oul3ALZ+| z*Y=-iZ0YA@7Jt_s87Vds*~|L7ySoQY=*LJp-&1zr@RDO_@kqAedWCtn4PEdLepnaqoL{hW;M}qIct6Y)%o98fy)*tX zXC$7HWH8Ur2IPk^fE>`auoi@VeeMo@8_D*U$cC@|a~v=)(5Jl8Jp(?%{($<>_b=i3 zln(SA+7PZ;7-LwEaO_ik0R4dcFc+V){XONvdi@dm6s%EUuD+%AzBWF>n)wmE3Vjy# z2CS8@UBlPTgY_tk{YZPo*V_HkdBT1ATl?-y_~2Uq{w@&q{b;-&VQ<4&LoVSyAlxfQ zqwj?L&<9xS!e{7%(4Q}fX0t;kV$A zii7Yw^hbVw3crI#>swG5b0IH~L;%2HPY7UP7i1Vl2QVJ{FTryJ_d$*+Bg79F#V~c4 zTmoRam>mGfh~zN2)CY`>BGMl)@a5wiym3=jA;cqfCwAA-r?57YJH@e@%x015mQfxnw0D|B)gi_{EI zKmkNx3_t`sImtV~H4qx}aB*TKV@eQzJf2?}U;r2O-F;cCkMb`<66_>F3g)6Orag|s zO=e>_3&Zj0DM?V?i(zJD9K2c!&%tz{&qZU*Kj08WWXvjn{8m=u;aTZd(AWo5~7=cdPVQ&RMOBiL~f0uIY5F(D~} zKMR1tHN#mz62_K=1=-x#%G_Mv!~hHb(DB*9A+65?nA<^(r+x!DL!_HN%0ABhC`&8@ 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 X(%+Ka7QfSZSNGoY{jL6l{{DXh+PGr@ diff --git a/Source/Images/Images.xcassets/AlertIcon.imageset/128X128.png b/Source/Images/Images.xcassets/AlertIcon.imageset/128X128.png index 1a599df979101d94f100c41c0ad1362746ec28a1..217bbbc2809f687e32c27942d153f1c9e83733d2 100644 GIT binary patch literal 8072 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 y%3C3?ClEM>Kbw# delta 1552 zcmV+r2JiXoBA^VABYyx1a7bBm000XT000XT0n*)m`~Uz2(n&-?RCt{2TU|^XRTTbq zL0c=*P>ZpkVp5tE`hu%3n$Yy6*##3Njpa!bqDg(z`U+`EQ=2}UzBO7&^huLSOynm% z*g#@m>_TGrNoW(dg4IHctQ6?N&Nb)m%b}I8P;iLnP0(GEml6!LnQBMP5ausv6N}Y7nV@TKqRT;ly7f7c7-f zycJ+<5RB~yIfyZ2*V4$Y{#&->31l1DNVaNrs~n0dz|sK?aS9-;2_SVaWY^PRnTsEW(F+txMIj_Ae+f{L4OH7R`;14&~UQ14NPeQQz+U& z*?6>FHv0~4yUK$&ac%~f9Ke7sT{w!8i})1^vYm6tR|Zo8XU+u4Tl&ZH4R}sL93qk8 z39p3%nDysMg%$%GI{^NYK6J1aeIF+n%3MKfIP=GfFyw>tEI@(+H>%W)M7#g4QhnQn9TOK9{h^aC2 z=zxz-y;*R5>FTJb=eYsrAQc0IR=`UYXn(z#OJBqA_;fh+pG#jU`gLGc;M4a{=$}{D z9^;E|e{uaRZ;-44el&v==w3Wnt6yLH;U;%ND1X@QX0T@89(*E{{^aaDz8gt~Ltcl= zAm~N`-H8}?f(9?rKhji>ntglK^Bx>&dV%K?^k}6|MkZ(Gd22u=_?%jT^nLRy z^?y>&^#*$mC4!*yH&f}0Ne>adeD)nrrc>fX$GydL!Ly~BQwG^qK+FT^PsGuCI*!Yy zxVP^va-~qc{*RyY*J2$f6=@D2huBps6Krw-LWwHij67ld)@@$@#Q;bx03oTC*u>1d zdX`IwVlDZYK<|-<>sA1coj|4X-e+IpP=9@`TnRYITx($M0 zh=eEtQU26q-djpbKf_}X3|9f3<lg8788U}us_42akdCb$Jn zkImh^kIQG@)s;Y`jbni{rsyX7Gx4$EVH*U4TuDwU=;`~0&lp*OMt;7!e$(@NO7RJN zdfW~a+%8=m!L{qZqWz6#J^0ZGlGM<5No@1XkSs{%L^zNMluFG6{ey>0@PA2JBP5jx zf+>S~5)#?Q{dxlsSz*h7H(|;RF$gceZrew;LHn?qMBiSl+fL3x`tYN!h;y`PM<%Wq! zv>J$p(v2t%Wy`A7q5#(QaC-PgvEP;4GE>)@sOT*-%hMz7x6FvBYl&CXrm5wbQGYf~ z8BuriWLa;UTb}(nm~C@UDt7$N(On0WfeqmS!2fl9Qg3(0yG{TA00{s|MNUMnLSTZy C3F;aE diff --git a/Source/Images/Images.xcassets/AppIcon.appiconset/1024X1024.png b/Source/Images/Images.xcassets/AppIcon.appiconset/1024X1024.png index cffcacb296d4b557db27400204768bd0c99dd380..4e703cf3005b8bcc67b8203fd9ed1de3f9b73434 100644 GIT binary patch literal 53724 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 OKb0%j6iXB=9{nHaZ;{sk literal 24519 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 literal 5348 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 diff --git a/Source/Images/Images.xcassets/AppIcon.appiconset/64X64.png b/Source/Images/Images.xcassets/AppIcon.appiconset/64X64.png index 2b3d950bfee38959be3b8aa7a30729fa66d575a9..e302ef22531c8b7c3830d623feef41ce55bf820b 100644 GIT binary patch literal 4460 zcmV-y5tHtTP)BE1ZQLxAY_vg;$?V;oX(lt{{G@3qJ`_O^!KmPq;Qw2}Cn0L9?Tam?khm;cmfj@-EE=vY<#k5o0f^m&*sM6pBI0EL{uRZtU4R5Z z`^X8`V)zNfJ8YaF;u6EP09P2E5Ad?jnUOMxUqvh^s*L``+lU9}BlcPZ2XpY-jyyG} zttleQMaed+noc9ry@UIIwng1*#M^^UkFzj+6yF25_LnMhmf?pGpH<}CbcnAmX|u%; z|7E#p3GpKUUAw%6atiT2#A8)^yd3Cssj4^8z$>TJnP(8+gLrkpJj%xIMEtceUz`ec zeo~wa<_{hK;NmJRuzL3+9o0iVC08s$)vACv7|KExH6jmbd4gn8^0FkllzkRT6fd|P0n zrwSVAXrbt(XrcJk;OyVRDf#{Yw^VBW$m{6QFhZvPBk<6w!tZERBfJcLd1$M3&)TehHS09YRtkN(Z(GDi^F2;vKBX;HY+Q*UEnTqUvgh@A9J5aYSek}G;8j! zqp=F+X_rylpF&#LEj%b>g+0Q~5^H*5N-?MwSLAe5T{?GXqX;9WfX;*4pXw6&V*io?m|<6;W0GAb4f<-njQ>$>Q>9MPr@ zwU=n+0I+*9IKpp9{{0u*d8K7@p9VPmCp-6AwmA>rtOL;dXv;Q$XXCx^0IWWyc=l3w zC&Xgk0yx!!!@*Zi8qcdA>-AfBZYACT*nGWS|NH%Vee(m<-vGFv{{#Oecnig2*2@3@ z061k>NoGw=04e|g00;m9hiL!=000010000Q0000000N)_00aO40096106?Gv00aO4 z0096106+i$003o~V2S_$4An_QK~#7F?OSP(TvZi5ua{XeONQC9P1eauSPDW2OAJBK zG76<8g;^0{zgX39WaEDO+0hJhUTQNm(ghx%|$hK9BDK%d&1ZJU+v+OvuL%y^f2b zC_G{q93X(Xxz?#&zG1D1_bK>TA*e+iL#%L6tk8fSBlldJ zp5L3Wyy5kW;VaUkx+d>gGAI(h?cAPC&!$o+B>`2JZ&>2>dUnFTl@Xwpn1cZ^gZ+?? zyV^2k^Hlwf(S7JcM}!x6{KeuaEa3(I2MHqW;jp=~ZQrH_x6VCoH$^sjJcjji2n6^r zgqsrX?-p~YFH&9IZM=Y|N(39xhdPD_x>aY@9T*e~Fu7rMy;-xY`5IJSLwBdO&7lBf z!JA^^%uUodxd$Ds(4gu}-4z3{L@_y(bR_`ZbQ>ZnLTW^S85}^v@nmraj-(p~hN>N> z5EOxhn35{#rIUbVU-tv*N;o}+6|7IokDK|<4MM7W5#1F7un>~~TXJm&0X3eXD$MhB zI(|6UhnZZRf9oUXfCDnJdt%x#snr}OP1zKz9C_6DQXu-j6PB&9MWBH5NO!M zFKjAk55{qEae>zD>mQT?zYlJlyAhN?lBhfa6R{-~CjnZ)X5Ky~waW97(kucsF~9(p z#pm@%X-Sb(SC-4n>6J2XR;|>}nj*97s*!(rsl28b4oCgWR!)l0xzMNGM_w+@5vGEu*u6|9IWi z(M?ecEJad<(I+;qcb8Vd-%y)yvo|5NLM*E|Z1$YLKOSN6Z7LIAC{{L*^)@psm$ z1vRKe!7{jE(5od0C>RP#YkQ9zJ$X(J96qJLqbFOW`D}+&V;DU0(>v4?=t(5(XPcgv zcMlwwqC&?EW@E^3w1L~l#Ok9$fZy+x9d8|!lg(|gdYM|7R;P7)`vw56Qw|?JEe8&r zl7k;MYrU@SJ_!LJBR)N%6qdjLo-ZISOpzfF%Ha6W6Mxenm_K-ewSn8mYzff%?u`;f zh(_zPt(~&{tp@qjb(hMa4^PX%#%2I+k|W2@%9(Rr62Npu&!87DMvx%>G+5&PJ>R%V zmR&qo1_px~A0B`DHF^5^U3#9}UryI_B`rLH>7?TH049D+9JScT=w+INA9M8+e|$}z zd}gP#w0FzUP)GwAqq-Ntt+2q^6~zq&!?NMaH_B%)?g^gO-tyv`^6NjmD)cNm$cRcj z0fSCqu39lbMT&jQtO8xRtEV3=`_#hOxGe#%TXUt{am#fw2*TKA>yG{Mi^pG9uj0(0 z@dQID91iP{;?N-3*_KRLTqJQCWVj_;Bp?S8U}-lu!%asa_?62S$=zRD17IfRc(3gH z&oR00;lD`Vz>vlUPK%5UQ>rJ+Jmd^hYbtbTz4QLDNbGP(O}+u$?P*YPIQL9l2%r!f zw0%q#JhE#BxyAL<<=(qKuVb8fz_F%Qx&N1cm9o-ex$L63vT)v1Su}5&%t7uz0!k(o zYWxTy*SPn=KgrhZ?@2+5VU?(zjln)9K6NJ`e%!dXOO%qs6%!@`+W6gdI_;4RO@Igt zgfK%S2|i|o(1YGV6$qSli;D_0&tL+Tv9L<4o_l?F0unsH88|X_oTfq;S>F0D-5^UB z)=6)G`wYki;Q`DPIYSJW$hhZl=s+@5UN%XyOHQ31G@jPXaky@}GaERMPAq8%up2rX zrRy%!GXvv#`>qCAebt3>0oDafc4*m7WP|M;y>h0dQ%;_4*Ic0qMx~Q&eK`+TUZG=+9Pc;`X*kl1@!zRaWY%5B z2yk!VP5|C1jfX<(U37dS_a4Z(AA)QWl!<;HIl92)8R8NKkWa8?~eSeysTK){ag^G zD&r&s6-kSXuh9q&mS(&TK&OO|Wnh%Bj+pQa#S*#GG_BQM-8UxelF<_FiyonQH+ zR8^EAzaP>xH(7cRJ46>Qm?1Y_yHr{u00+ zKh>IL>YLQNVvjeX6NqC@DXg4dX&B}PR8?{ePaz0!9Vtn@rM@H(Zj#ay>gP<+w?Hc{ ztCv+P7Ax1>7Uw%aKL7YNOXaIyyk0Z7Gc8^6z$4GeAl3u+gB^|S z@tV35AT|MbbukJA(7N0ne|`4{c=>TcW=^Zp*JG2h#{bxri?J$PpsTzxjvG{d_r^UxD>a5};n-N`S*B%IjCp$GU7k9u(2{4c}xO96aTx4_eu~I=9|@ zwcNgTrH};f`f!-EU`bq!_X>WWPj>7+DEB<@l#agG@W{1Sz7`L4DFL8h0Q*Z9cQfp#zxdjta87G-n%lf(n|$+!o8|Sl52<_eH5tJZY->l4JiPhu_&9^_2pu-c zO$!@G!gM$+A_W1cK(A5H8Lm89XI%GlJ%9M<8TsCQznA;48Oog?-s7`Jf4fyaJaJC< z21eYcjUxq?6oV^8?GbB}D1doA7d+3tyhnEJJuFuv7Mw-k+x>21eo;SBeDum@fR|2h zau84$mfoOY;bTApF(5h=3|1Q(Qt0Bed>h=JL@?UtAM~P_^#HKy_Ds4sz^wj)u+bY| zgXfMR&{EaYhL%kVm;%h96ar@5I78W;Y%}wh5rc>03NzhXL>F%5DPJ7O#mvIA+f%X zZ|mA2K?PYuZD6%0DuegN;}4LbBX!kb3*Curq4&T0F8z;Ch)YbShkOYg}&JF=CNr}C?PC36DRzOacun;WAI#4HgY y%3C3?ClEM>Kbw# delta 1552 zcmV+r2JiXoBA^VABYyx1a7bBm000XT000XT0n*)m`~Uz2(n&-?RCt{2TU|^XRTTbq zL0c=*P>ZpkVp5tE`hu%3n$Yy6*##3Njpa!bqDg(z`U+`EQ=2}UzBO7&^huLSOynm% z*g#@m>_TGrNoW(dg4IHctQ6?N&Nb)m%b}I8P;iLnP0(GEml6!LnQBMP5ausv6N}Y7nV@TKqRT;ly7f7c7-f zycJ+<5RB~yIfyZ2*V4$Y{#&->31l1DNVaNrs~n0dz|sK?aS9-;2_SVaWY^PRnTsEW(F+txMIj_Ae+f{L4OH7R`;14&~UQ14NPeQQz+U& z*?6>FHv0~4yUK$&ac%~f9Ke7sT{w!8i})1^vYm6tR|Zo8XU+u4Tl&ZH4R}sL93qk8 z39p3%nDysMg%$%GI{^NYK6J1aeIF+n%3MKfIP=GfFyw>tEI@(+H>%W)M7#g4QhnQn9TOK9{h^aC2 z=zxz-y;*R5>FTJb=eYsrAQc0IR=`UYXn(z#OJBqA_;fh+pG#jU`gLGc;M4a{=$}{D z9^;E|e{uaRZ;-44el&v==w3Wnt6yLH;U;%ND1X@QX0T@89(*E{{^aaDz8gt~Ltcl= zAm~N`-H8}?f(9?rKhji>ntglK^Bx>&dV%K?^k}6|MkZ(Gd22u=_?%jT^nLRy z^?y>&^#*$mC4!*yH&f}0Ne>adeD)nrrc>fX$GydL!Ly~BQwG^qK+FT^PsGuCI*!Yy zxVP^va-~qc{*RyY*J2$f6=@D2huBps6Krw-LWwHij67ld)@@$@#Q;bx03oTC*u>1d zdX`IwVlDZYK<|-<>sA1coj|4X-e+IpP=9@`TnRYITx($M0 zh=eEtQU26q-djpbKf_}X3|9f3<lg8788U}us_42akdCb$Jn zkImh^kIQG@)s;Y`jbni{rsyX7Gx4$EVH*U4TuDwU=;`~0&lp*OMt;7!e$(@NO7RJN zdfW~a+%8=m!L{qZqWz6#J^0ZGlGM<5No@1XkSs{%L^zNMluFG6{ey>0@PA2JBP5jx zf+>S~5)#?Q{dxlsSz*h7H(|;RF$gceZrew;LHn?qMBiSl+fL3x`tYN!h;y`PM<%Wq! zv>J$p(v2t%Wy`A7q5#(QaC-PgvEP;4GE>)@sOT*-%hMz7x6FvBYl&CXrm5wbQGYf~ z8BuriWLa;UTb}(nm~C@UDt7$N(On0WfeqmS!2fl9Qg3(0yG{TA00{s|MNUMnLSTZy C3F;aE diff --git a/Source/Images/PlainBopomofo.tiff b/Source/Images/PlainBopomofo.tiff index 2aee9334b9ca69115810f7dd4dcb99b9e5efe879..c16a981d76ce1e16cbf0e4afea52e43a43551d1a 100644 GIT binary patch delta 1673 zcmZWpdrVVj6hHR?Ep06bC+9SGub!s{-$D$|cF!Dhh+UNR`;+2~XAd1pnB<676HMHdwcmC@O>eY5PK zt#aSfCxAEsmeu@xuR07Py<-U>BQGLqFT^g}_vZsTV+*|xX!gv!8fM@Oy%HNoBWzi> z1vfn`e-^sv)>$ET<-P>wuQk3`e9s@N!{sjcVb9EPbgkRvzBEQ>Gl!!euT-rjO`9&X z1a4UTD)rUT&d5L&Y*evXtGe zH6!Sn(Hqs{h3nIzsP-$G&<2{l_ilnB*gF7tUGn%)PBQ^| z&K3{STjM5@tZnm76au3mTZWz%Xt|$OaakUq2>>~J?h>kmgn*cS&_vJx*!E*&yg%uI zs|&+aNOP5pbO)brQT(Q5G+CA|WA?MH@2WOZvO{n={-H=a=L^@^FaAABWO8Dnz{Oy6RZ` z?H$=M_274LAmVtmcJTRzE^ul21#(VU#AR)Wa62NGjaRlLs~7=@2aF6sX?TApr(U@O zIHjxi$Q-@4Gv2_Z4V^-WE<4l@sJiL&CXgpYMf_NPM1q~VKl7j9MxOj*kQid}awA=E zH+ws+ zsiglo@42q|g`6&ooZmW9#OkPMb<4vanGcjHgBW;hd)#bB$zpaOu?iKr5DrqdH=aeA3vzKa{x@YY!Qo;i5Bedt$eAXItWpqMc79!F!#MUYl-01t9?Vb0pKzfyanO(JtRSKz;}CliE9hI&|SkgW2b2 zDL90Tww|!KD(RfO63+p!tipMQu&in`3<}6VC)sj>cSUDmPln4SK@It1dEBSu%ONNz7~HWT84^CeJpRNpI#) zRLw6o=mb`^Q6S6Ap(5-5!pQ{hVy3fBhih`w7EP8<&Sa4& z*(R2(m>SZQo>EHCX_nW}cS!OnO-s}k^;D5K~*r-(=5grf4@A|zogC+J^%m! delta 1131 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 ZqwGGTZlVl+3?lren))#gqh`Uze*jFzYx4jA literal 7502 zcmchb2|QG5AIG0_W*>uL#=ebx3z1#2Zy~aiie?6585&!=7O8O4Dod-nl@zU%+ahUq z747R)UZv%Bb&J;9yyv0OynQ0ww>m!0d4BV3-~V=o!`~km0YE2%kC%k1w-2wTfQ{^Bgb^d%@&$PFA=JZp4{*=VlM8B zhzg zQ1;jShdhz*u!rZ1>OfSBU+e!LzR35X7|Y|8?;5RaARN09V6xr~#s=#L%lmN0KW9S@^c(u0xqR0T z<$s9{^DWAk1Gngh@xR1|zQY=T{Q8rvUuDDf!;Q1}y204+yi+Y#`}lK9_x%w5P;B^p zBZ~dko;zskxB5R68=eL04f5Nr-5!?j-@^Z#4Sj{V7O%0IBj@}0YpRZYSN`X0Xg91c z=qt=Ov;j8JbH`h}?Yr^6#D;w6qbS#q3u6vH59d1%?iV%jy8c`b#un{`eu{h+vA@N4 zc+Tfc(dVzRMK-`5GGXnB*Mj)`P+G(j@4tmT)Y-$C6X!zxGuhBS@%N9P&4%wEqW7<# z&4#|i_b>Pk2;T*VldJE!#xOXi7qUecv<1fFdosRs7G#M0?qka$0N^l%02X#b#5CHc z@z{R}dJ){macnU{{HRe(Q^m$*0Jau82LLi6D>g3oQDd`+{6`I03ZnINfG7h(|6ck7 zL}frfLsZ4iG5Dwhz7)66MJP@^!uOaW#frWK)%@7E*x z*7R#o`oW@DvK;4At#aWqLOym48otMSqW@Gv?rgf4tQ+SwujcH~IH#Qvr zJPnR#aM=l%9*Jq1ASB2Q)1$HU(&EMR6fvE_orc*7fLm(XbOA3uA%kVWHe{LG+1awZ zx!K9wj0}^&SawpZfWvZ2O-YO8PY0miXSfTId0e(B4Cui1)dd492Nz;IQ^*^L9m|~?7n_-!!Gb|$rzWRn3RvlB zv1~5O7O5I2r1;PNq47*=IGems_LK8KgVOXah8{J}E(FSm)=0A+t& zV}h~e17J=z1@sF{fcvu<;29DC7rh>vK?d^XM+pP)$qUzb*kAXU#^(F(|8ql&u#adu zFP;U5-GV|`>`Xxx)YvltZv;RE(trt+fI83y2EYVZ02|-{oPh@z3j#n0hy;@W8zg`f zAOP857MKU}Kmk|=R)aFI0c-)4pc?E2b>J{K4o-pd;3BvJZh>~t37&v%&%1f7V+qf^ltXfC=GEkP^LooFq396gU-LEF(M zXb<)aKmsR^)5MwJY;kV509+I<4ky6P!R6zMaTT~~Ts`hI?h>vY_Z0UAPsB6uEW8Qc z0q>0u#k28K@pJHn_%i%|#EBZ-N`nZ!ckI${m6f!IpyAoh?*BqfqD$&nO5iX~-`@<^qm zoup%=Hc}_)HCckJLAD@!lB39c@_ceJc?bC@xsCjg{EkAW=u+${0Td2pI;DuRg;Gzs zKg%#d6vSt)r!@~-4-DTb7(l#digDo3hZs#dB^ z>X|e}T36addXn@E>0;@9(ifzk(kXO3x*I)~K8IdTKSaMq?~!51n9Bsnq{tM??2tJt z(9#;He&%}3*#iClSyRiGkute%mU^vW(%`hPFBuR zE=+EkT)Es4xpsM+yq>&|e6oCz{9gI1@^2N?6kHUz3QH8K71|VDDXJ(sD{>X{6>Ai) zD85x1%~*&DW~Yx}#0iw$+Z)UZvfr-L0dcqj;^HZeA9ZLZleZ3Ar!ZO_;d>|E_~><-(#vA4F*u-|L{mxGBz zlEV&%uF?9Vxuds??r_v{jCI`P*gl3eCT7gWG3`#8PO(lEPWPR)ojJ~1ogcawx+J>n zbb01F(pBJk!1a}zz1wWJ26vphmwUc@i-)X7s7IN{T~95~c+V=&ZZ9jZnO+Uv1aDvO z<=)qP)O^@Jl|J2LZN|K z-NRRew?~*o%#Ju8sT|3RJQzia3X9qr)ic3k!s-c~(N@uU(N`zxPn?Gw$$&-#u zmYy6td0z}6COl?G%=_4Y*oxSmDLzxyPU&X5vDdJlaGW`-IFGoF+!fr1agK2-;=1A; z<5$K%N^nXjPI$_5iy5lR{33Njb!qV7_3#;;^@&tL;7C9{1uo%CXx431=s3oOKKIC)q&lQX;C@y$k z$Syqhi}^1lzX+GcEo~{XE?U2gxQxH->T;*$+gHe}n6aW`rT@y>RjgI{t6r|2y!vdh zW$}hJ)HRuF+DrUO4wmYaE-QUsmQZ%3+_k)Bt?Jq(YhSJ7tZQ5EyuNya+J=G+Z#VKb zUf<-kskTC|qIffLbJpeuTf(-S{?+!^%B{*<3%35VjlZpZd(if#N}I~c9jZHus?e&; zsz*DccV67(zUyGMN%f{0`I>^=V0Y&3$9rP-T;1!tw{f5CzUuwD`_~>|9LTRlYNywB z*Co`oAB;TMdT8vS#(IbP1HYO6w(YRy;qoJLM~aS8kLDf~9-DEjry;H3>GAmE9gUM4 zZ=HxZak*)HQ_D%;lV?tOo;q>b<@B*Lj%N;^wLg34ob9=~^Va8Uo2{A;{BHI8ffnnQ z+6y)p4qmjoSl>Fj^=O+@+wn{8mrh;wxqSZ0xGSw!L$6-H7JaS#I_G-VjpQ58Z)V+m zcWdr#!tMM!^gG3ORqs~(VfaT)yLJ1Kd+zs|?+4$%)xqv~@}Knoyzk6?Ao-y9p~l0? zE{m?ik31h;d_3WC=aaN2@1ExUDf8#rX9myq|K _walkedNodes; // user override model - McBopomofo::UserOverrideModel *_uom; + vChewing::UserOverrideModel *_uom; // the latest composing buffer that is updated to the foreground app NSMutableString *_composingBuffer; diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index b599fdf09..5d9b3f1b6 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1,7 +1,7 @@ // // InputMethodController.m // -// Copyright (c) 2011 The McBopomofo Project. +// Copyright (c) 2021 The vChewing Project. // // Contributors: // Mengjuei Hsieh (@mjhsieh) @@ -41,7 +41,7 @@ #import "AppDelegate.h" #import "VTHorizontalCandidateController.h" #import "VTVerticalCandidateController.h" -#import "McBopomofo-Swift.h" +#import "vChewing-Swift.h" //@import SwiftUI; @@ -76,9 +76,9 @@ static NSString *const kCandidateListTextSizeKey = @"CandidateListTextSize"; static NSString *const kSelectPhraseAfterCursorAsCandidatePreferenceKey = @"SelectPhraseAfterCursorAsCandidate"; static NSString *const kUseHorizontalCandidateListPreferenceKey = @"UseHorizontalCandidateList"; static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize"; +static NSString *const kDisableUserCandidateSelectionLearning = @"DisableUserCandidateSelectionLearning"; static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey"; static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey"; -static NSString *const kDisableUserCandidateSelectionLearning = @"DisableUserCandidateSelectionLearning"; // advanced (usually optional) settings static NSString *const kCandidateTextFontName = @"CandidateTextFontName"; @@ -86,8 +86,8 @@ static NSString *const kCandidateKeyLabelFontName = @"CandidateKeyLabelFontName" static NSString *const kCandidateKeys = @"CandidateKeys"; // input modes -static NSString *const kBopomofoModeIdentifier = @"org.openvanilla.inputmethod.McBopomofo.Bopomofo"; -static NSString *const kPlainBopomofoModeIdentifier = @"org.openvanilla.inputmethod.McBopomofo.PlainBopomofo"; +static NSString *const kBopomofoModeIdentifier = @"org.openvanilla.inputmethod.vChewing.Bopomofo"; +static NSString *const kPlainBopomofoModeIdentifier = @"org.openvanilla.inputmethod.vChewing.PlainBopomofo"; // key code enums enum { @@ -111,17 +111,13 @@ VTCandidateController *gCurrentCandidateController = nil; // if DEBUG is defined, a DOT file (GraphViz format) will be written to the // specified path everytime the grid is walked #if DEBUG -static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot"; +static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; #endif // shared language model object that stores our phrase-term probability database FastLM gLanguageModel; FastLM gLanguageModelPlainBopomofo; -static const int kUserOverrideModelCapacity = 500; -static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. -McBopomofo::UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); - // https://clang-analyzer.llvm.org/faq.html __attribute__((annotate("returns_localized_nsstring"))) static inline NSString *LocalizationNotNeeded(NSString *s) { @@ -129,14 +125,17 @@ static inline NSString *LocalizationNotNeeded(NSString *s) { } // private methods -@interface McBopomofoInputMethodController () +@interface vChewingInputMethodController () + (VTHorizontalCandidateController *)horizontalCandidateController; + (VTVerticalCandidateController *)verticalCandidateController; - (void)collectCandidates; - (size_t)actualCandidateCursorIndex; +- (NSString *)neighborTrigramString; +- (void)_performDeferredSaveUserCandidatesDictionary; +- (void)saveUserCandidatesDictionary; - (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client; - (void)beep; @@ -153,37 +152,7 @@ public: } }; -static const double kEpsilon = 0.000001; - -static double FindHighestScore(const vector& nodes, double epsilon) { - double highestScore = 0.0; - for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { - double score = ni->node->highestUnigramScore(); - if (score > highestScore) { - highestScore = score; - } - } - return highestScore + epsilon; -} - -static void OverrideCandidate(const vector& nodes, const string& candidateValue, bool fixed, double floatingNodeOverrideScore) { - for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { - const vector& candidates = (*ni).node->candidates(); - for (size_t i = 0, c = candidates.size(); i < c; ++i) { - if (candidates[i].value == candidateValue) { - // found our node - if (fixed) { - const_cast((*ni).node)->selectCandidateAtIndex(i); - } else { - const_cast((*ni).node)->selectFloatingCandidateAtIndex(i, floatingNodeOverrideScore); - } - return; - } - } - } -} - -@implementation McBopomofoInputMethodController +@implementation vChewingInputMethodController - (void)dealloc { // clean up everything @@ -213,7 +182,6 @@ static void OverrideCandidate(const vector& nodes, const string& can // create the lattice builder _languageModel = &gLanguageModel; _builder = new BlockReadingBuilder(_languageModel); - _uom = &gUserOverrideModel; // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -221,6 +189,11 @@ static void OverrideCandidate(const vector& nodes, const string& can // create the composing buffer _composingBuffer = [[NSMutableString alloc] init]; + // populate the settings, by default, DISABLE user candidate learning + if (![[NSUserDefaults standardUserDefaults] objectForKey:kDisableUserCandidateSelectionLearning]) { + [[NSUserDefaults standardUserDefaults] setObject:(id)kCFBooleanTrue forKey:kDisableUserCandidateSelectionLearning]; + } + _inputMode = kBopomofoModeIdentifier; _chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; } @@ -232,7 +205,7 @@ static void OverrideCandidate(const vector& nodes, const string& can { // a menu instance (autoreleased) is requested every time the user click on the input menu NSMenu *menu = [[NSMenu alloc] initWithTitle:LocalizationNotNeeded(@"Input Method Menu")]; - NSMenuItem *preferenceMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"McBopomofo Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; + NSMenuItem *preferenceMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; [menu addItem:preferenceMenuItem]; // If Option key is pressed, show the learning-related menu @@ -267,7 +240,7 @@ static void OverrideCandidate(const vector& nodes, const string& can NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; [menu addItem:updateCheckItem]; - NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About McBopomofo…", @"") action:@selector(showAbout:) keyEquivalent:@""]; + NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""]; [menu addItem:aboutMenuItem]; return menu; @@ -720,15 +693,15 @@ static void OverrideCandidate(const vector& nodes, const string& can // then walk the lattice [self popOverflowComposingTextAndWalk:client]; - // get user override model suggestion - string overrideValue = - (_inputMode == kPlainBopomofoModeIdentifier) ? "" : - _uom->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); - if (!overrideValue.empty()) { - size_t cursorIndex = [self actualCandidateCursorIndex]; - vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); - double highestScore = FindHighestScore(nodes, kEpsilon); - OverrideCandidate(nodes, overrideValue, false, highestScore); + // see if we need to override the selection if a learned one exists + if (![[NSUserDefaults standardUserDefaults] boolForKey:kDisableUserCandidateSelectionLearning]) { + NSString *trigram = [self neighborTrigramString]; + + // Lookup from the user dict to see if the trigram fit or not + NSString *overrideCandidateString = [gCandidateLearningDictionary objectForKey:trigram]; + if (overrideCandidateString) { + [self candidateSelected:(NSAttributedString *)overrideCandidateString]; + } } // then update the text @@ -1307,6 +1280,61 @@ static void OverrideCandidate(const vector& nodes, const string& can return cursorIndex; } +- (NSString *)neighborTrigramString +{ + // gather the "trigram" for user candidate selection learning + + NSMutableArray *termArray = [NSMutableArray array]; + + size_t cursorIndex = [self actualCandidateCursorIndex]; + vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); + + const Node* prev = 0; + const Node* current = 0; + const Node* next = 0; + + size_t wni = 0; + size_t wnc = _walkedNodes.size(); + size_t accuSpanningLength = 0; + for (wni = 0; wni < wnc; wni++) { + NodeAnchor& anchor = _walkedNodes[wni]; + if (!anchor.node) { + continue; + } + + accuSpanningLength += anchor.spanningLength; + if (accuSpanningLength >= cursorIndex) { + prev = current; + current = anchor.node; + break; + } + + current = anchor.node; + } + + if (wni + 1 < wnc) { + next = _walkedNodes[wni + 1].node; + } + + string term; + if (prev) { + term = prev->currentKeyValue().key; + [termArray addObject:[NSString stringWithUTF8String:term.c_str()]]; + } + + if (current) { + term = current->currentKeyValue().key; + [termArray addObject:[NSString stringWithUTF8String:term.c_str()]]; + } + + if (next) { + term = next->currentKeyValue().key; + [termArray addObject:[NSString stringWithUTF8String:term.c_str()]]; + } + + return [termArray componentsJoinedByString:@"-"]; +} + - (void)_performDeferredSaveUserCandidatesDictionary { BOOL __unused success = [gCandidateLearningDictionary writeToFile:gUserCandidatesDictionaryPath atomically:YES]; @@ -1330,13 +1358,13 @@ static void OverrideCandidate(const vector& nodes, const string& can BOOL useHorizontalCandidateList = [[NSUserDefaults standardUserDefaults] boolForKey:kUseHorizontalCandidateListPreferenceKey]; if (useVerticalMode) { - gCurrentCandidateController = [McBopomofoInputMethodController verticalCandidateController]; + gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; } else if (useHorizontalCandidateList) { - gCurrentCandidateController = [McBopomofoInputMethodController horizontalCandidateController]; + gCurrentCandidateController = [vChewingInputMethodController horizontalCandidateController]; } else { - gCurrentCandidateController = [McBopomofoInputMethodController verticalCandidateController]; + gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; } // set the attributes for the candidate panel (which uses NSAttributedString) @@ -1467,15 +1495,17 @@ static void OverrideCandidate(const vector& nodes, const string& can // candidate selected, override the node with selection string selectedValue = [[_candidates objectAtIndex:index] UTF8String]; - size_t cursorIndex = [self actualCandidateCursorIndex]; - if (_inputMode != kPlainBopomofoModeIdentifier) { - _uom->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); + if (![[NSUserDefaults standardUserDefaults] boolForKey:kDisableUserCandidateSelectionLearning]) { + NSString *trigram = [self neighborTrigramString]; + NSString *selectedNSString = [NSString stringWithUTF8String:selectedValue.c_str()]; + [gCandidateLearningDictionary setObject:selectedNSString forKey:trigram]; + [self saveUserCandidatesDictionary]; } + + size_t cursorIndex = [self actualCandidateCursorIndex]; _builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue); - vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); - OverrideCandidate(nodes, selectedValue, true, 0.0); [_candidates removeAllObjects]; [self walk]; @@ -1491,7 +1521,7 @@ static void OverrideCandidate(const vector& nodes, const string& can static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm) { - NSString *dataPath = [[NSBundle bundleForClass:[McBopomofoInputMethodController class]] pathForResource:filenameWithoutExtension ofType:@"txt"]; + NSString *dataPath = [[NSBundle bundleForClass:[vChewingInputMethodController class]] pathForResource:filenameWithoutExtension ofType:@"txt"]; bool result = lm.open([dataPath UTF8String]); if (!result) { NSLog(@"Failed opening language model: %@", dataPath); @@ -1518,7 +1548,7 @@ void LTLoadLanguageModel() } NSString *appSupportPath = [paths objectAtIndex:0]; - NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"McBopomofo"]; + NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"]; BOOL isDir = NO; BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:userDictPath isDirectory:&isDir]; diff --git a/Source/Installer/AppDelegate.h b/Source/Installer/AppDelegate.h index 32b4936f8..20a063315 100644 --- a/Source/Installer/AppDelegate.h +++ b/Source/Installer/AppDelegate.h @@ -1,7 +1,7 @@ // // AppDelegate.h // -// Copyright (c) 2011-2012 The McBopomofo Project. +// Copyright (c) 2011-2012 The vChewing Project. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/Installer/AppDelegate.m b/Source/Installer/AppDelegate.m index 17485951f..428e9fcf7 100644 --- a/Source/Installer/AppDelegate.m +++ b/Source/Installer/AppDelegate.m @@ -1,7 +1,7 @@ // // AppDelegate.m // -// Copyright (c) 2011-2012 The McBopomofo Project. +// Copyright (c) 2011-2012 The vChewing Project. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -29,12 +29,12 @@ #import #import "OVInputSourceHelper.h" -static NSString *const kTargetBin = @"McBopomofo"; +static NSString *const kTargetBin = @"vChewing"; static NSString *const kTargetType = @"app"; -static NSString *const kTargetBundle = @"McBopomofo.app"; +static NSString *const kTargetBundle = @"vChewing.app"; static NSString *const kDestinationPartial = @"~/Library/Input Methods/"; -static NSString *const kTargetPartialPath = @"~/Library/Input Methods/McBopomofo.app"; -static NSString *const kTargetFullBinPartialPath = @"~/Library/Input Methods/McBopomofo.app/Contents/MacOS/McBopomofo"; +static NSString *const kTargetPartialPath = @"~/Library/Input Methods/vChewing.app"; +static NSString *const kTargetFullBinPartialPath = @"~/Library/Input Methods/vChewing.app/Contents/MacOS/vChewing"; static const NSTimeInterval kTranslocationRemovalTickInterval = 0.5; static const NSTimeInterval kTranslocationRemovalDeadline = 60.0; @@ -231,13 +231,13 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) { } if (warning) { - RunAlertPanel(NSLocalizedString(@"Attention", nil), NSLocalizedString(@"McBopomofo is upgraded, but please log out or reboot for the new version to be fully functional.", nil), NSLocalizedString(@"OK", nil)); + RunAlertPanel(NSLocalizedString(@"Attention", nil), NSLocalizedString(@"vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", nil), NSLocalizedString(@"OK", nil)); } else { // Only prompt a warning if pre-macOS 12. The flag is not indicative of anything meaningful due to the need of user intervention in Prefernces.app on macOS 12. if (!mainInputSourceEnabled && !isMacOS12OrAbove) { RunAlertPanel(NSLocalizedString(@"Warning", nil), NSLocalizedString(@"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", nil), NSLocalizedString(@"Continue", nil)); } else { - RunAlertPanel(NSLocalizedString(@"Installation Successful", nil), NSLocalizedString(@"McBopomofo is ready to use.", nil), NSLocalizedString(@"OK", nil)); + RunAlertPanel(NSLocalizedString(@"Installation Successful", nil), NSLocalizedString(@"vChewing is ready to use.", nil), NSLocalizedString(@"OK", nil)); } } diff --git a/Source/Installer/ArchiveUtil.h b/Source/Installer/ArchiveUtil.h index aab19c611..e6c38bc73 100644 --- a/Source/Installer/ArchiveUtil.h +++ b/Source/Installer/ArchiveUtil.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011-2019 The McBopomofo Project. +// Copyright (c) 2011-2019 The vChewing Project. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/Installer/ArchiveUtil.m b/Source/Installer/ArchiveUtil.m index 90d47e35c..278387326 100644 --- a/Source/Installer/ArchiveUtil.m +++ b/Source/Installer/ArchiveUtil.m @@ -1,4 +1,4 @@ -// Copyright (c) 2011-2019 The McBopomofo Project. +// Copyright (c) 2011-2019 The vChewing Project. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/Installer/Base.lproj/MainMenu.xib b/Source/Installer/Base.lproj/MainMenu.xib index 000f018d1..072ea56f0 100644 --- a/Source/Installer/Base.lproj/MainMenu.xib +++ b/Source/Installer/Base.lproj/MainMenu.xib @@ -15,10 +15,10 @@ - - + + - + @@ -33,7 +33,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -146,7 +146,7 @@ Gw - + diff --git a/Source/Installer/en.lproj/InfoPlist.strings b/Source/Installer/en.lproj/InfoPlist.strings index 6472f62de..080e9bd08 100644 --- a/Source/Installer/en.lproj/InfoPlist.strings +++ b/Source/Installer/en.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ /* Localized versions of Info.plist keys */ -CFBundleName = "Install McBopomofo"; +CFBundleName = "Install vChewing"; NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; diff --git a/Source/Installer/en.lproj/License.rtf b/Source/Installer/en.lproj/License.rtf index 72e324611..7c5a2ee46 100644 --- a/Source/Installer/en.lproj/License.rtf +++ b/Source/Installer/en.lproj/License.rtf @@ -5,7 +5,7 @@ \margl1440\margr1440\vieww16860\viewh12620\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 -\f0\b\fs36 \cf0 McBopomofo License Agreement +\f0\b\fs36 \cf0 vChewing License Agreement \f1\b0\fs24 \ \ Copyright \'a9 2011-2021 Mengjuei Hsieh et al.\ diff --git a/Source/Installer/en.lproj/Localizable.strings b/Source/Installer/en.lproj/Localizable.strings index b3f28f520..d7db1566d 100644 --- a/Source/Installer/en.lproj/Localizable.strings +++ b/Source/Installer/en.lproj/Localizable.strings @@ -23,11 +23,11 @@ "OK" = "OK"; /* No comment provided by engineer. */ -"McBopomofo is ready to use." = "McBopomofo is ready to use."; +"vChewing is ready to use." = "vChewing is ready to use."; "Stopping the old version. This may take up to one minute…" = "Stopping the old version. This may take up to one minute…"; "Attention" = "Attention"; -"McBopomofo is upgraded, but please log out or reboot for the new version to be fully functional." = "McBopomofo is upgraded, but please log out or reboot for the new version to be fully functional."; +"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing is upgraded, but please log out or reboot for the new version to be fully functional."; "Fatal Error" = "Fatal Error"; "Abort" = "Abort"; diff --git a/Source/Installer/main.m b/Source/Installer/main.m index 103bf28f8..f7f3a4666 100644 --- a/Source/Installer/main.m +++ b/Source/Installer/main.m @@ -1,7 +1,7 @@ // // main.m // -// Copyright (c) 2011-2012 The McBopomofo Project. +// Copyright (c) 2011-2012 The vChewing Project. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/Installer/zh-Hans.lproj/InfoPlist.strings b/Source/Installer/zh-Hans.lproj/InfoPlist.strings new file mode 100644 index 000000000..7f7803f6c --- /dev/null +++ b/Source/Installer/zh-Hans.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Localized versions of Info.plist keys */ + +CFBundleName = "安装威注音"; +NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; + diff --git a/Source/Installer/zh-Hans.lproj/License.rtf b/Source/Installer/zh-Hans.lproj/License.rtf new file mode 100644 index 000000000..423fb2503 --- /dev/null +++ b/Source/Installer/zh-Hans.lproj/License.rtf @@ -0,0 +1,42 @@ +{\rtf1\ansi\ansicpg950\cocoartf2636 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Bold;\f1\fnil\fcharset0 HelveticaNeue;\f2\fnil\fcharset136 PingFangTC-Regular; +} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\margl1440\margr1440\vieww16860\viewh12620\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 + +\f0\b\fs36 \cf0 vChewing License Agreement +\f1\b0\fs24 \ +\ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 + +\f2 \cf0 \'b5\'db\'a7\'40\uc0\u26435 \'a7\'51\'a9\'d2\'a6\'b3 +\f1 \'a9 2011-2021 Mengjuei Hsieh +\f2 \'b5\'a5\'a4\'48 +\f1 \ +\ + +\f2 \'ab\'c2\'aa\'60\'ad\'b5\uc0\u36755 \'a4\'4a\'aa\'6b\u32500 \u25252 \'a4\'48\u23385 \'a7\'d3\u36149 \u23545 \u35813 \u20135 \'ab\'7e\'aa\'ba\'b5\'7b\'a7\'c7\'b3\'a1\'a4\'c0\'a4\'a3\'a8\'c9\'a6\'b3\'a5\'f4\'a6\'f3\'a9\'d2\'a6\'b3\u26435 \'a1\'43 +\f1 \ +\ + +\f2 \uc0\u36719 \'a5\'f3\'aa\'ba\'b5\'db\'a7\'40\u26435 \'a7\'51\'a4\'48\'a8\'cc\'a6\'b9\'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\'a1\'41\u23558 \'a8\'e4\u23545 \'a4\'5f\u36719 \'a5\'f3\'aa\'ba\'b5\'db\'a7\'40\u26435 \'a7\'51\'b1\'c2\u26435 \u37322 \'a5\'58\'a1\'41\'a5\'75\'ad\'6e\'a8\'cf\'a5\'ce\'aa\'cc\u36341 \'bc\'69\'a5\'48\'a4\'55\'a4\'47\u39033 \'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\u21465 \'a9\'fa\'aa\'ba\u20041 \u21153 \'a9\'ca\u35268 \'a9\'77\'a1\'41\'a8\'e4\'a7\'59\'a8\'c9\'a6\'b3\u23545 \'a6\'b9\u36719 \'a5\'f3\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ac\'db\u20851 \u35828 \'a9\'fa\'a4\'e5\u26723 \'a6\'db\'a5\'d1\'a4\'a3\'a8\'fc\'ad\'ad\'a8\'ee\'a6\'61\u36827 \'a6\'e6\'a7\'51\'a5\'ce\'aa\'ba\u26435 \'a7\'51\'a1\'41\'ad\'53\u22260 \'a5\'5d\'ac\'41\'a1\'75\'a8\'cf\'a5\'ce\'a1\'42\'ad\'ab\'a8\'ee\'a1\'42\'ad\'d7\'a7\'ef\'a1\'42\'a6\'58\'a6\'7d\'a1\'42\'a5\'58\'aa\'a9\'a1\'42\'b4\'b2\'a5\'ac\'a1\'42\'a6\'41\'b1\'c2\u26435 \'a1\'42\'a4\'ce\u36137 \'b0\'e2\'b5\'7b\'a6\'a1\'ad\'ab\'a8\'ee\'a7\'40\'ab\'7e\'a1\'76\'b5\'a5\u35832 \'a6\'68\'a4\'e8\'ad\'b1\'aa\'ba\u24212 \'a5\'ce\'a1\'41\'a6\'d3\'b4\'b2\'a5\'ac\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a1\'42\'a7\'f3\'a5\'69\u23558 \'a4\'57\'ad\'7a\u26435 \'a7\'51\u20256 \u36882 \'a4\'a9\'a8\'e4\'a6\'5a\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'aa\'ba\'a6\'5a\'a4\'e2\'a1\'41\'ad\'d5\'ad\'59\'a8\'e4\'a6\'5a\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a5\'e7\'aa\'41\'c1\'74\'a5\'48\'a4\'55\'a4\'47\u39033 \'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\'aa\'ba\u20041 \u21153 \'a9\'ca\u35268 \'a9\'77\'a1\'41\u21017 \'a8\'e4\u23545 \'b5\'7b\'a6\'a1\'a5\'e7\'a8\'c9\'a6\'b3\'c9\'4f\'ab\'65\'a4\'e2\u36816 \'a5\'ce\'ad\'53\u22260 \'ac\'db\'a6\'50\'aa\'ba\'a6\'50\'a4\'40\u26435 \'a7\'51\'a1\'43 +\f1 \ +\ + +\f2 \'b4\'b2\'a5\'ac\'a6\'b9\'a4\'40\uc0\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'cc\'a1\'41\u39035 \u23558 \'a5\'bb\u26465 \'b4\'da\'a8\'e4\'a4\'57\'aa\'ba\'a1\'75\'b5\'db\'a7\'40\u26435 \u22768 \'a9\'fa\'a1\'76\'a4\'ce\'a5\'48\'a4\'55\'aa\'ba\'a1\'75\'a7\'4b\u36131 \u22768 \'a9\'fa\'a1\'76\'a1\'41\u20869 \'b4\'4f\'a4\'5f\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'ce\'a8\'e4\'ad\'ab\'a8\'ee\'a7\'40\'ab\'7e\'aa\'ba\u23454 \'ca\'5e\'a4\'a7\'a4\'a4\'a1\'43 +\f1 \ +\ + +\f2 \'a6\'5d\'b3\'c2\'b2\'7a\uc0\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'ba\'b1\'c2\u26435 \'bc\'d2\'a6\'a1\'a4\'44\'ac\'4f\u26080 \u20607 \'b4\'a3\'a8\'d1\'a1\'41\'ac\'4f\'a5\'48\'a6\'62\u29616 \'a6\'e6\'aa\'6b\'ab\'df\'aa\'ba\'ac\'5b\'cc\'db\'a4\'55\'a5\'69\'a5\'48\'a5\'44\u24352 \'a6\'58\'b2\'7a\'aa\'ba\'a7\'4b\'b0\'a3\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'43\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'aa\'ba\'b5\'db\'a7\'40\u26435 \'a4\'48\'a9\'ce\'a5\'f4\'a6\'f3\'aa\'ba\'a6\'5a\u32493 \'b4\'b2\'a5\'ac\'aa\'cc\'a1\'41\u23545 \'a4\'5f\'a8\'e4\'a9\'d2\'b4\'b2\'a5\'ac\'aa\'ba\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'ac\'d2\'a4\'a3\u36127 \'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\u23454 \u36136 \'a4\'57\'aa\'ba\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'41\'a9\'fa\'a5\'dc\'a5\'e7\'a9\'ce\u38544 \'b3\'eb\'a1\'42\'b0\'d3\u19994 \'a7\'51\'a5\'ce\'a9\'ca\'a5\'e7\'a9\'ce\'af\'53\'a9\'77\'a5\'d8\'aa\'ba\'a8\'cf\'a5\'ce\'a9\'ca\'a1\'41\u36825 \'a8\'c7\'a7\'a1\'a4\'a3\'a6\'62\'ab\'4f\'bb\'d9\'a4\'a7\'a6\'43\'a1\'43\'a7\'51\'a5\'ce\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'ba\'a9\'d2\'a6\'b3\u39118 \u38505 \'a7\'a1\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\u25285 \u36127 \'a1\'43\'b0\'b2\'a6\'70\'a9\'d2\'a8\'cf\'a5\'ce\'aa\'ba\'b3\'c2\'b2\'7a\'b5\'7b\'a7\'c7\u21457 \'a5\'cd\'af\'ca\'b3\'b4\'a9\'ca\u38382 \u39064 \'a1\'41\'a8\'cf\'a5\'ce\'aa\'cc\'bb\'dd\'a6\'db\'a6\'e6\u25285 \u36127 \'ad\'d7\'a5\'bf\'a1\'42\'a7\'ef\'a5\'bf\'a4\'ce\'a5\'b2\'ad\'6e\'aa\'ba\'aa\'41\u21153 \'a4\'e4\'a5\'58\'a1\'43\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'ba\'b5\'db\'a7\'40\u26435 \'a4\'48\'a4\'a3\u36127 \'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\u23454 \u36136 \'a4\'57\'aa\'ba\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'41\u26080 \u35770 \'a5\'f4\'a6\'f3\'a4\'40\'af\'eb\'aa\'ba\'a1\'42\'af\'53\'ae\'ed\'aa\'ba\'a1\'42\'b0\'b8\u21457 \'aa\'ba\'a1\'42\'a6\'5d\'aa\'47\u20851 \'a8\'74\'a6\'a1\'aa\'ba\u25439 \'ae\'60\'a1\'41\'a9\'ce\'ac\'4f\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'ba\'a4\'a3\'d3\'ec\'a5\'ce\'a9\'ca\'a1\'41\'a7\'a1\u39035 \'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\u36127 \u25285 \'a1\'43 +\f1 \ +\ +Copyright \'a9 2011-2021 Mengjuei Hsieh et al.\ +\ +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'93Software\'94), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ +\ +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ +} \ No newline at end of file diff --git a/Source/Installer/zh-Hans.lproj/Localizable.strings b/Source/Installer/zh-Hans.lproj/Localizable.strings new file mode 100644 index 000000000..e1e271824 --- /dev/null +++ b/Source/Installer/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* No comment provided by engineer. */ +"%@ (for version %@)" = "%1$@ (%2$@ 版)"; + +/* No comment provided by engineer. */ +"Agree and Upgrade" = "同意并升级"; + +/* No comment provided by engineer. */ +"Cancel" = "取消"; + +/* No comment provided by engineer. */ +"Cannot activate the input method." = "无法启用输入法。"; + +/* No comment provided by engineer. */ +"Cannot copy the file to the destination." = "无法将输入法拷贝至目的地。"; + +/* No comment provided by engineer. */ +"Install Failed" = "安装失败"; + +/* No comment provided by engineer. */ +"Installation Successful" = "安装成功"; + +/* No comment provided by engineer. */ +"OK" = "好"; + +/* No comment provided by engineer. */ +"vChewing is ready to use." = "威注音输入法安装成功"; + +"Finish" = "结束"; +"Attention" = "请注意"; +"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing 安装完成,但建议您登出或重新开机,以便顺利使用新版。"; + +"Fatal Error" = "安装错误"; +"Abort" = "放弃安装"; +"Cannot register input source %@ at %@." = "无法从档案位置 %2$@ 安装输入法 \"%1$@\"。"; +"Cannot find input source %@ after registration." = "在注册完输入法 \"%@\" 仍然无法找到输入法。"; + +"Warning" = "安装不完整"; +"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "输入法已经安装好,但可能没有完全启用。请从“系统偏好设定” > “键盘” > “输入方式”分页加入输入法。"; +"Continue" = "继续"; diff --git a/Source/Installer/zh-Hans.lproj/MainMenu.xib b/Source/Installer/zh-Hans.lproj/MainMenu.xib new file mode 100644 index 000000000..d90801fde --- /dev/null +++ b/Source/Installer/zh-Hans.lproj/MainMenu.xib @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Installer/zh-Hant.lproj/InfoPlist.strings b/Source/Installer/zh-Hant.lproj/InfoPlist.strings index 929742bd6..f034b1a51 100644 --- a/Source/Installer/zh-Hant.lproj/InfoPlist.strings +++ b/Source/Installer/zh-Hant.lproj/InfoPlist.strings @@ -1,5 +1,5 @@ /* Localized versions of Info.plist keys */ -CFBundleName = "安裝小麥注音"; +CFBundleName = "安裝威注音"; NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; diff --git a/Source/Installer/zh-Hant.lproj/License.rtf b/Source/Installer/zh-Hant.lproj/License.rtf index 24d7d652b..a0ff200a6 100644 --- a/Source/Installer/zh-Hant.lproj/License.rtf +++ b/Source/Installer/zh-Hant.lproj/License.rtf @@ -1,15 +1,15 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820 -{\fonttbl\f0\fnil\fcharset0 LucidaGrande-Bold;\f1\fnil\fcharset0 LucidaGrande;\f2\fnil\fcharset136 PingFangTC-Regular; +{\rtf1\ansi\ansicpg950\cocoartf2636 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Bold;\f1\fnil\fcharset0 HelveticaNeue;\f2\fnil\fcharset136 PingFangTC-Regular; } {\colortbl;\red255\green255\blue255;} {\*\expandedcolortbl;;} \margl1440\margr1440\vieww16860\viewh12620\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 -\f0\b\fs36 \cf0 McBopomofo License Agreement +\f0\b\fs36 \cf0 vChewing License Agreement \f1\b0\fs24 \ \ -Copyright \'a9 2011-2021 Mengjuei Hsieh et al.\ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 \f2 \cf0 \'b5\'db\'a7\'40\'c5\'76\'a7\'51\'a9\'d2\'a6\'b3 \f1 \'a9 2011-2021 Mengjuei Hsieh @@ -17,9 +17,11 @@ Copyright \'a9 2011-2021 Mengjuei Hsieh et al.\ \f1 \ \ -\f2 \'b3\'6e\'c5\'e9\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'a4\'48\'a8\'cc\'a6\'b9 -\f1 MIT -\f2 \'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'a1\'41\'b1\'4e\'a8\'e4\'b9\'ef\'a9\'f3\'b3\'6e\'c5\'e9\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'b1\'c2\'c5\'76\'c4\'c0\'a5\'58\'a1\'41\'a5\'75\'ad\'6e\'a8\'cf\'a5\'ce\'aa\'cc\'bd\'ee\'bc\'69\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'b1\'d4\'a9\'fa\'aa\'ba\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'a8\'e4\'a7\'59\'a8\'c9\'a6\'b3\'b9\'ef\'a6\'b9\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ac\'db\'c3\'f6\'bb\'a1\'a9\'fa\'a4\'e5\'c0\'c9\'a6\'db\'a5\'d1\'a4\'a3\'a8\'fc\'ad\'ad\'a8\'ee\'a6\'61\'b6\'69\'a6\'e6\'a7\'51\'a5\'ce\'aa\'ba\'c5\'76\'a7\'51\'a1\'41\'bd\'64\'b3\'f2\'a5\'5d\'ac\'41\'a1\'75\'a8\'cf\'a5\'ce\'a1\'42\'ad\'ab\'bb\'73\'a1\'42\'ad\'d7\'a7\'ef\'a1\'42\'a6\'58\'a8\'d6\'a1\'42\'a5\'58\'aa\'a9\'a1\'42\'b4\'b2\'a5\'ac\'a1\'42\'a6\'41\'b1\'c2\'c5\'76\'a1\'42\'a4\'ce\'b3\'63\'b0\'e2\'b5\'7b\'a6\'a1\'ad\'ab\'bb\'73\'a7\'40\'ab\'7e\'a1\'76\'b5\'a5\'bd\'d1\'a6\'68\'a4\'e8\'ad\'b1\'aa\'ba\'c0\'b3\'a5\'ce\'a1\'41\'a6\'d3\'b4\'b2\'a5\'ac\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a1\'42\'a7\'f3\'a5\'69\'b1\'4e\'a4\'57\'ad\'7a\'c5\'76\'a7\'51\'b6\'c7\'bb\'bc\'a4\'a9\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'aa\'ba\'ab\'e1\'a4\'e2\'a1\'41\'ad\'d5\'ad\'59\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a5\'e7\'aa\'41\'c1\'74\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'aa\'ba\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'ab\'68\'a8\'e4\'b9\'ef\'b5\'7b\'a6\'a1\'a5\'e7\'a8\'c9\'a6\'b3\'bb\'50\'ab\'65\'a4\'e2\'b9\'42\'a5\'ce\'bd\'64\'b3\'f2\'ac\'db\'a6\'50\'aa\'ba\'a6\'50\'a4\'40\'c5\'76\'a7\'51\'a1\'43 +\f2 \'ab\'c2\'aa\'60\'ad\'b5\'bf\'e9\'a4\'4a\'aa\'6b\'ba\'fb\'c5\'40\'a4\'48\'ae\'5d\'a7\'d3\'b6\'51\'b9\'ef\'b8\'d3\'b2\'a3\'ab\'7e\'aa\'ba\'b5\'7b\'a6\'a1\'b3\'a1\'a4\'c0\'a4\'a3\'a8\'c9\'a6\'b3\'a5\'f4\'a6\'f3\'a9\'d2\'a6\'b3\'c5\'76\'a1\'43 +\f1 \ +\ + +\f2 \'b3\'6e\'c5\'e9\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'a4\'48\'a8\'cc\'a6\'b9\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'a1\'41\'b1\'4e\'a8\'e4\'b9\'ef\'a9\'f3\'b3\'6e\'c5\'e9\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'b1\'c2\'c5\'76\'c4\'c0\'a5\'58\'a1\'41\'a5\'75\'ad\'6e\'a8\'cf\'a5\'ce\'aa\'cc\'bd\'ee\'bc\'69\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'b1\'d4\'a9\'fa\'aa\'ba\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'a8\'e4\'a7\'59\'a8\'c9\'a6\'b3\'b9\'ef\'a6\'b9\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ac\'db\'c3\'f6\'bb\'a1\'a9\'fa\'a4\'e5\'c0\'c9\'a6\'db\'a5\'d1\'a4\'a3\'a8\'fc\'ad\'ad\'a8\'ee\'a6\'61\'b6\'69\'a6\'e6\'a7\'51\'a5\'ce\'aa\'ba\'c5\'76\'a7\'51\'a1\'41\'bd\'64\'b3\'f2\'a5\'5d\'ac\'41\'a1\'75\'a8\'cf\'a5\'ce\'a1\'42\'ad\'ab\'bb\'73\'a1\'42\'ad\'d7\'a7\'ef\'a1\'42\'a6\'58\'a8\'d6\'a1\'42\'a5\'58\'aa\'a9\'a1\'42\'b4\'b2\'a5\'ac\'a1\'42\'a6\'41\'b1\'c2\'c5\'76\'a1\'42\'a4\'ce\'b3\'63\'b0\'e2\'b5\'7b\'a6\'a1\'ad\'ab\'bb\'73\'a7\'40\'ab\'7e\'a1\'76\'b5\'a5\'bd\'d1\'a6\'68\'a4\'e8\'ad\'b1\'aa\'ba\'c0\'b3\'a5\'ce\'a1\'41\'a6\'d3\'b4\'b2\'a5\'ac\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a1\'42\'a7\'f3\'a5\'69\'b1\'4e\'a4\'57\'ad\'7a\'c5\'76\'a7\'51\'b6\'c7\'bb\'bc\'a4\'a9\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'aa\'ba\'ab\'e1\'a4\'e2\'a1\'41\'ad\'d5\'ad\'59\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a5\'e7\'aa\'41\'c1\'74\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'aa\'ba\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'ab\'68\'a8\'e4\'b9\'ef\'b5\'7b\'a6\'a1\'a5\'e7\'a8\'c9\'a6\'b3\'bb\'50\'ab\'65\'a4\'e2\'b9\'42\'a5\'ce\'bd\'64\'b3\'f2\'ac\'db\'a6\'50\'aa\'ba\'a6\'50\'a4\'40\'c5\'76\'a7\'51\'a1\'43 \f1 \ \ @@ -27,26 +29,14 @@ Copyright \'a9 2011-2021 Mengjuei Hsieh et al.\ \f1 \ \ -\f2 \'a6\'5d -\f1 MIT -\f2 \'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'b1\'c2\'c5\'76\'bc\'d2\'a6\'a1\'a4\'44\'ac\'4f\'b5\'4c\'c0\'76\'b4\'a3\'a8\'d1\'a1\'41\'ac\'4f\'a5\'48\'a6\'62\'b2\'7b\'a6\'e6\'aa\'6b\'ab\'df\'aa\'ba\'ac\'5b\'ba\'63\'a4\'55\'a5\'69\'a5\'48\'a5\'44\'b1\'69\'a6\'58\'b2\'7a\'aa\'ba\'a7\'4b\'b0\'a3\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'43 -\f1 MIT -\f2 \'b3\'6e\'c5\'e9\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a9\'ce\'a5\'f4\'a6\'f3\'aa\'ba\'ab\'e1\'c4\'f2\'b4\'b2\'a5\'ac\'aa\'cc\'a1\'41\'b9\'ef\'a9\'f3\'a8\'e4\'a9\'d2\'b4\'b2\'a5\'ac\'aa\'ba -\f1 MIT -\f2 \'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'ac\'d2\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'aa\'ba\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'a9\'fa\'a5\'dc\'a5\'e7\'a9\'ce\'c1\'f4\'b3\'eb\'a1\'42\'b0\'d3\'b7\'7e\'a7\'51\'a5\'ce\'a9\'ca\'a5\'e7\'a9\'ce\'af\'53\'a9\'77\'a5\'d8\'aa\'ba\'a8\'cf\'a5\'ce\'a9\'ca\'a1\'41\'b3\'6f\'a8\'c7\'a7\'a1\'a4\'a3\'a6\'62\'ab\'4f\'bb\'d9\'a4\'a7\'a6\'43\'a1\'43\'a7\'51\'a5\'ce -\f1 MIT -\f2 \'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'a9\'d2\'a6\'b3\'ad\'b7\'c0\'49\'a7\'a1\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'a1\'43\'b0\'b2\'a6\'70\'a9\'d2\'a8\'cf\'a5\'ce\'aa\'ba -\f1 MIT -\f2 \'b5\'7b\'a6\'a1\'b5\'6f\'a5\'cd\'af\'ca\'b3\'b4\'a9\'ca\'b0\'dd\'c3\'44\'a1\'41\'a8\'cf\'a5\'ce\'aa\'cc\'bb\'dd\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'ad\'d7\'a5\'bf\'a1\'42\'a7\'ef\'a5\'bf\'a4\'ce\'a5\'b2\'ad\'6e\'aa\'ba\'aa\'41\'b0\'c8\'a4\'e4\'a5\'58\'a1\'43 -\f1 MIT -\f2 \'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'aa\'ba\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'b5\'4c\'bd\'d7\'a5\'f4\'a6\'f3\'a4\'40\'af\'eb\'aa\'ba\'a1\'42\'af\'53\'ae\'ed\'aa\'ba\'a1\'42\'b0\'b8\'b5\'6f\'aa\'ba\'a1\'42\'a6\'5d\'aa\'47\'c3\'f6\'ab\'59\'a6\'a1\'aa\'ba\'b7\'6c\'ae\'60\'a1\'41\'a9\'ce\'ac\'4f -\f1 MIT -\f2 \'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'a4\'a3\'be\'41\'a5\'ce\'a9\'ca\'a1\'41\'a7\'a1\'b6\'b7\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'ad\'74\'be\'e1\'a1\'43 +\f2 \'a6\'5d\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'b1\'c2\'c5\'76\'bc\'d2\'a6\'a1\'a4\'44\'ac\'4f\'b5\'4c\'c0\'76\'b4\'a3\'a8\'d1\'a1\'41\'ac\'4f\'a5\'48\'a6\'62\'b2\'7b\'a6\'e6\'aa\'6b\'ab\'df\'aa\'ba\'ac\'5b\'ba\'63\'a4\'55\'a5\'69\'a5\'48\'a5\'44\'b1\'69\'a6\'58\'b2\'7a\'aa\'ba\'a7\'4b\'b0\'a3\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'43\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a9\'ce\'a5\'f4\'a6\'f3\'aa\'ba\'ab\'e1\'c4\'f2\'b4\'b2\'a5\'ac\'aa\'cc\'a1\'41\'b9\'ef\'a9\'f3\'a8\'e4\'a9\'d2\'b4\'b2\'a5\'ac\'aa\'ba\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'ac\'d2\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'aa\'ba\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'a9\'fa\'a5\'dc\'a5\'e7\'a9\'ce\'c1\'f4\'b3\'eb\'a1\'42\'b0\'d3\'b7\'7e\'a7\'51\'a5\'ce\'a9\'ca\'a5\'e7\'a9\'ce\'af\'53\'a9\'77\'a5\'d8\'aa\'ba\'a8\'cf\'a5\'ce\'a9\'ca\'a1\'41\'b3\'6f\'a8\'c7\'a7\'a1\'a4\'a3\'a6\'62\'ab\'4f\'bb\'d9\'a4\'a7\'a6\'43\'a1\'43\'a7\'51\'a5\'ce\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'a9\'d2\'a6\'b3\'ad\'b7\'c0\'49\'a7\'a1\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'a1\'43\'b0\'b2\'a6\'70\'a9\'d2\'a8\'cf\'a5\'ce\'aa\'ba\'b3\'c2\'b2\'7a\'b5\'7b\'a6\'a1\'b5\'6f\'a5\'cd\'af\'ca\'b3\'b4\'a9\'ca\'b0\'dd\'c3\'44\'a1\'41\'a8\'cf\'a5\'ce\'aa\'cc\'bb\'dd\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'ad\'d7\'a5\'bf\'a1\'42\'a7\'ef\'a5\'bf\'a4\'ce\'a5\'b2\'ad\'6e\'aa\'ba\'aa\'41\'b0\'c8\'a4\'e4\'a5\'58\'a1\'43\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'aa\'ba\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'b5\'4c\'bd\'d7\'a5\'f4\'a6\'f3\'a4\'40\'af\'eb\'aa\'ba\'a1\'42\'af\'53\'ae\'ed\'aa\'ba\'a1\'42\'b0\'b8\'b5\'6f\'aa\'ba\'a1\'42\'a6\'5d\'aa\'47\'c3\'f6\'ab\'59\'a6\'a1\'aa\'ba\'b7\'6c\'ae\'60\'a1\'41\'a9\'ce\'ac\'4f\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'a4\'a3\'be\'41\'a5\'ce\'a9\'ca\'a1\'41\'a7\'a1\'b6\'b7\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'ad\'74\'be\'e1\'a1\'43 \f1 \ \ +Copyright \'a9 2011-2021 Mengjuei Hsieh et al.\ +\ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'93Software\'94), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ \ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ \ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ -} +} \ No newline at end of file diff --git a/Source/Installer/zh-Hant.lproj/Localizable.strings b/Source/Installer/zh-Hant.lproj/Localizable.strings index 5a492caa1..3fd65cbb1 100644 --- a/Source/Installer/zh-Hant.lproj/Localizable.strings +++ b/Source/Installer/zh-Hant.lproj/Localizable.strings @@ -23,11 +23,11 @@ "OK" = "好"; /* No comment provided by engineer. */ -"McBopomofo is ready to use." = "小麥注音輸入法安裝成功"; +"vChewing is ready to use." = "威注音輸入法安裝成功"; "Finish" = "結束"; "Attention" = "請注意"; -"McBopomofo is upgraded, but please log out or reboot for the new version to be fully functional." = "McBopomofo 安裝完成,但建議您登出或重新開機,以便順利使用新版。"; +"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing 安裝完成,但建議您登出或重新開機,以便順利使用新版。"; "Fatal Error" = "安裝錯誤"; "Abort" = "放棄安裝"; diff --git a/Source/Installer/zh-Hant.lproj/MainMenu.xib b/Source/Installer/zh-Hant.lproj/MainMenu.xib index c9bcd182c..bb333e832 100644 --- a/Source/Installer/zh-Hant.lproj/MainMenu.xib +++ b/Source/Installer/zh-Hant.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -15,10 +15,10 @@ - - + + - + @@ -33,7 +33,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -81,22 +81,23 @@ + - + - + - + - - + + @@ -109,16 +110,16 @@ - - - - + - + - + @@ -166,6 +167,7 @@ Gw + @@ -181,20 +183,20 @@ Gw - + - + - + - + diff --git a/Source/OpenCCBridge.swift b/Source/OpenCCBridge.swift index a9c639890..72a257404 100644 --- a/Source/OpenCCBridge.swift +++ b/Source/OpenCCBridge.swift @@ -1,7 +1,7 @@ import Foundation import OpenCC -// Since SwiftyOpenCC only provide Swift classes, we create an NSObject subclass +// Since SwiftyLibreCC only provide Swift classes, we create an NSObject subclass // in Swift in order to bridge the Swift classes into our Objective-C++ project. class OpenCCBridge : NSObject { private static let shared = OpenCCBridge() diff --git a/Source/PreferencesWindowController.h b/Source/PreferencesWindowController.h index 538ddd5b4..4d04222f0 100644 --- a/Source/PreferencesWindowController.h +++ b/Source/PreferencesWindowController.h @@ -1,7 +1,7 @@ // // PreferencesWindowController.h // -// Copyright (c) 2011 The McBopomofo Project. +// Copyright (c) 2021 The vChewing Project. // // Contributors: // Mengjuei Hsieh (@mjhsieh) diff --git a/Source/PreferencesWindowController.m b/Source/PreferencesWindowController.m index 292531961..0dd1feb93 100644 --- a/Source/PreferencesWindowController.m +++ b/Source/PreferencesWindowController.m @@ -1,7 +1,7 @@ // // PreferencesWindowController.m // -// Copyright (c) 2011 The McBopomofo Project. +// Copyright (c) 2021 The vChewing Project. // // Contributors: // Mengjuei Hsieh (@mjhsieh) diff --git a/Source/README b/Source/README index 823841342..9476b1d98 100644 --- a/Source/README +++ b/Source/README @@ -140,7 +140,7 @@ │   ├── Bopomofo@2x.tiff │   ├── BopomofoTextMenu.tiff │   ├── BopomofoTextMenu@2x.tiff -│   ├── McBopomofo.iconset +│   ├── vChewing.iconset │   │   ├── icon_128x128.png │   │   ├── icon_128x128@2x.png │   │   ├── icon_16x16.png @@ -169,13 +169,18 @@ │   │   ├── Localizable.strings │   │   └── MainMenu.xib │   ├── main.m +│   ├── zh-Hans.lproj +│   │   ├── InfoPlist.strings +│   │   ├── License.rtf +│   │   ├── Localizable.strings +│   │   └── MainMenu.xib │   └── zh-Hant.lproj │   ├── InfoPlist.strings │   ├── License.rtf │   ├── Localizable.strings │   └── MainMenu.xib -├── McBopomofo-Info.plist -├── McBopomofo-Prefix.pch +├── vChewing-Info.plist +├── vChewing-Prefix.pch ├── OVInputSourceHelper.h ├── OVInputSourceHelper.m ├── PreferencesWindowController.h @@ -192,6 +197,12 @@ │   ├── MainMenu.xib │   ├── UpdateNotificationController.xib │   └── preferences.xib +├── zh-Hans.lproj +│   ├── InfoPlist.strings +│   ├── Localizable.strings +│   ├── MainMenu.xib +│   ├── UpdateNotificationController.xib +│   └── preferences.xib ├── main.m └── zh-Hant.lproj ├── InfoPlist.strings diff --git a/Source/UpdateNotificationController.h b/Source/UpdateNotificationController.h index 4392bee88..1ad29610b 100644 --- a/Source/UpdateNotificationController.h +++ b/Source/UpdateNotificationController.h @@ -1,7 +1,7 @@ // // UpdateNotificationController.h // -// Copyright (c) 2011 The McBopomofo Project. +// Copyright (c) 2021 The vChewing Project. // // Contributors: // Mengjuei Hsieh (@mjhsieh) diff --git a/Source/UpdateNotificationController.m b/Source/UpdateNotificationController.m index 766365928..e69ee3179 100644 --- a/Source/UpdateNotificationController.m +++ b/Source/UpdateNotificationController.m @@ -1,7 +1,7 @@ // // UpdateNotificationController.m // -// Copyright (c) 2011 The McBopomofo Project. +// Copyright (c) 2021 The vChewing Project. // // Contributors: // Mengjuei Hsieh (@mjhsieh) diff --git a/Source/UserOverrideModel.cpp b/Source/UserOverrideModel.cpp index 8b2df5220..aa0a0b4d8 100644 --- a/Source/UserOverrideModel.cpp +++ b/Source/UserOverrideModel.cpp @@ -1,7 +1,7 @@ // // UserOverrideModel.cpp // -// Copyright (c) 2017 The McBopomofo Project. +// Copyright (c) 2017 The vChewing Project. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -31,7 +31,7 @@ #include #include -using namespace McBopomofo; +using namespace vChewing; // About 20 generations. static const double DecayThreshould = 1.0 / 1048576.0; diff --git a/Source/UserOverrideModel.h b/Source/UserOverrideModel.h index 0b981923a..c0f700b42 100644 --- a/Source/UserOverrideModel.h +++ b/Source/UserOverrideModel.h @@ -1,7 +1,7 @@ // // UserOverrideModel.h // -// Copyright (c) 2017 The McBopomofo Project. +// Copyright (c) 2017 The vChewing Project. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -34,7 +34,7 @@ #include "Gramambular.h" -namespace McBopomofo { +namespace vChewing { using namespace Formosa::Gramambular; @@ -75,7 +75,7 @@ private: std::map::iterator> m_lruMap; }; -}; // namespace McBopomofo +}; // namespace vChewing #endif diff --git a/Source/en.lproj/InfoPlist.strings b/Source/en.lproj/InfoPlist.strings index 889f77494..dddeda74a 100644 --- a/Source/en.lproj/InfoPlist.strings +++ b/Source/en.lproj/InfoPlist.strings @@ -1,6 +1,6 @@ -CFBundleName = "McBopomofo"; -CFBundleDisplayName = "McBopomofo"; +CFBundleName = "vChewing"; +CFBundleDisplayName = "vChewing"; NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; -"org.openvanilla.inputmethod.McBopomofo.Bopomofo" = "Bopomofo"; -"org.openvanilla.inputmethod.McBopomofo.PlainBopomofo" = "Plain Bopomofo"; +"org.openvanilla.inputmethod.vChewing.Bopomofo" = "vChewing"; +"org.openvanilla.inputmethod.vChewing.PlainBopomofo" = "Plain vChewing"; diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 4fdc110c5..48445416d 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* No comment provided by engineer. */ -"About McBopomofo…" = "About McBopomofo…"; +"About vChewing…" = "About vChewing…"; /* No comment provided by engineer. */ "Clear Learning Dictionary (%ju Items)" = "Clear Learning Dictionary (%ju Items)"; @@ -11,7 +11,7 @@ "Enable Selection Learning" = "Enable Selection Learning"; /* No comment provided by engineer. */ -"McBopomofo Preferences" = "McBopomofo Preferences"; +"vChewing Preferences" = "vChewing Preferences"; /* No comment provided by engineer. */ "Check Later" = "Check Later"; @@ -23,7 +23,7 @@ "Check for Update Completed" = "Check for Update Completed"; /* No comment provided by engineer. */ -"You are already using the latest version of McBopomofo." = "You are already using the latest version of McBopomofo."; +"You are already using the latest version of vChewing." = "You are already using the latest version of vChewing."; /* No comment provided by engineer. */ "Update Check Failed" = "Update Check Failed"; @@ -47,6 +47,6 @@ "Visit Website" = "Visit Website"; /* No comment provided by engineer. */ -"You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@" = "You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@"; +"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@"; "Chinese Conversion" = "Convert to Simplified Chinese"; diff --git a/Source/main.m b/Source/main.m index bca49bd0f..1dd9cdff4 100644 --- a/Source/main.m +++ b/Source/main.m @@ -1,7 +1,7 @@ // // main.m // -// Copyright (c) 2011 The McBopomofo Project. +// Copyright (c) 2021 The vChewing Project. // // Contributors: // Mengjuei Hsieh (@mjhsieh) @@ -35,7 +35,7 @@ #import #import "OVInputSourceHelper.h" -static NSString *const kConnectionName = @"McBopomofo_1_Connection"; +static NSString *const kConnectionName = @"vChewing_1_Connection"; int main(int argc, char *argv[]) { diff --git a/Source/McBopomofo-Bridging-Header.h b/Source/vChewing-Bridging-Header.h similarity index 100% rename from Source/McBopomofo-Bridging-Header.h rename to Source/vChewing-Bridging-Header.h diff --git a/Source/McBopomofo-Info.plist b/Source/vChewing-Info.plist similarity index 84% rename from Source/McBopomofo-Info.plist rename to Source/vChewing-Info.plist index 199cc0077..28d16f61a 100644 --- a/Source/McBopomofo-Info.plist +++ b/Source/vChewing-Info.plist @@ -24,7 +24,7 @@ tsInputModeListKey - org.openvanilla.inputmethod.McBopomofo.Bopomofo + org.openvanilla.inputmethod.vChewing.Bopomofo TISDoubleSpaceSubstitution @@ -32,6 +32,7 @@ Bopomofo.tiff tsInputModeCharacterRepertoireKey + Hans Hant Han @@ -50,7 +51,7 @@ tsInputModeScriptKey smTradChinese TISIntendedLanguage - zh-Hant + zh-Hans tsInputModeCharacterRepertoireKey Hant @@ -59,7 +60,7 @@ tsInputModeKeyEquivalentModifiersKey 4608 - org.openvanilla.inputmethod.McBopomofo.PlainBopomofo + org.openvanilla.inputmethod.vChewing.PlainBopomofo TISDoubleSpaceSubstitution @@ -67,6 +68,7 @@ PlainBopomofo.tiff tsInputModeCharacterRepertoireKey + Hans Hant Han @@ -85,7 +87,7 @@ tsInputModeScriptKey smTradChinese TISIntendedLanguage - zh-Hant + zh-Hans tsInputModeCharacterRepertoireKey Hant @@ -97,16 +99,16 @@ tsVisibleInputModeOrderedArrayKey - org.openvanilla.inputmethod.McBopomofo.Bopomofo - org.openvanilla.inputmethod.McBopomofo.PlainBopomofo + org.openvanilla.inputmethod.vChewing.Bopomofo + org.openvanilla.inputmethod.vChewing.PlainBopomofo InputMethodConnectionName - McBopomofo_1_Connection + vChewing_1_Connection InputMethodServerControllerClass - McBopomofoInputMethodController + vChewingInputMethodController InputMethodServerDelegateClass - McBopomofoInputMethodController + vChewingInputMethodController InputMethodServerPreferencesWindowControllerClass PreferencesWindowController LSApplicationCategoryType @@ -126,19 +128,20 @@ TICapsLockLanguageSwitchCapable TISInputSourceID - org.openvanilla.inputmethod.McBopomofo + org.openvanilla.inputmethod.vChewing TISIntendedLanguage - zh-Hant + zh-Hans TISParticipatesInTouchBar UpdateInfoEndpoint - https://mcbopomofo.openvanilla.org/updates/Info.plist + https://vchewing.openvanilla.org/updates/Info.plist UpdateInfoSite - https://mcbopomofo.openvanilla.org/ + https://vchewing.openvanilla.org/ tsInputMethodCharacterRepertoireKey - Hant Hans + Hant + Han tsInputMethodIconFileKey Bopomofo.tiff diff --git a/Source/McBopomofo-Prefix.pch b/Source/vChewing-Prefix.pch similarity index 38% rename from Source/McBopomofo-Prefix.pch rename to Source/vChewing-Prefix.pch index ca6fec958..fe236fd96 100644 --- a/Source/McBopomofo-Prefix.pch +++ b/Source/vChewing-Prefix.pch @@ -1,5 +1,5 @@ // -// Prefix header for all source files of the 'McBopomofo' target in the 'McBopomofo' project +// Prefix header for all source files of the 'vChewing' target in the 'vChewing' project // #ifdef __OBJC__ diff --git a/Source/zh-Hans.lproj/InfoPlist.strings b/Source/zh-Hans.lproj/InfoPlist.strings new file mode 100644 index 000000000..83b9178f9 --- /dev/null +++ b/Source/zh-Hans.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +CFBundleName = "威注音"; +CFBundleDisplayName = "威注音"; +NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; +"org.openvanilla.inputmethod.vChewing.Bopomofo" = "威注音"; +"org.openvanilla.inputmethod.vChewing.PlainBopomofo" = "ㄅ半全注"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings new file mode 100644 index 000000000..2855d2326 --- /dev/null +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,52 @@ +/* No comment provided by engineer. */ +"About vChewing…" = "关于威注音…"; + +/* No comment provided by engineer. */ +"Clear Learning Dictionary (%ju Items)" = "清除学习辞典 (%ju 个项目)"; + +/* No comment provided by engineer. */ +"Dump Learning Data to Console" = "将学习辞典内容输出到 Console 上"; + +/* No comment provided by engineer. */ +"Enable Selection Learning" = "使用自动学习功能"; + +/* No comment provided by engineer. */ +"vChewing Preferences" = "威注音偏好设定"; + +/* No comment provided by engineer. */ +"Check Later" = "晚点再通知我"; + +/* No comment provided by engineer. */ +"Check for Updates…" = "检查是否有新版…"; + +/* No comment provided by engineer. */ +"Check for Update Completed" = "新版检查完毕"; + +/* No comment provided by engineer. */ +"You are already using the latest version of vChewing." = "目前使用的已经是最新版本。"; + +/* No comment provided by engineer. */ +"Update Check Failed" = "无法检查新版"; + +/* No comment provided by engineer. */ +"There may be no internet connection or the server failed to respond.\n\nError message: %@" = "网路连线失败,或是伺服器没有回应。\n\n错误说明:%@"; + +/* No comment provided by engineer. */ +"OK" = "好"; + +/* No comment provided by engineer. */ +"Dismiss" = "关闭本视窗"; + +/* No comment provided by engineer. */ +"New Version Available" = "有新版可下载"; + +/* No comment provided by engineer. */ +"Not Now" = "以后再说"; + +/* No comment provided by engineer. */ +"Visit Website" = "前往网站"; + +/* No comment provided by engineer. */ +"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音版本是 %1$@ (%2$@),网路上有更新版本 %3$@ (%4$@) 可供下载。是否要前往威注音网站下载新版来安装?%5$@"; + +"Chinese Conversion" = "强制简体字输出"; diff --git a/Source/zh-Hans.lproj/MainMenu.xib b/Source/zh-Hans.lproj/MainMenu.xib new file mode 100644 index 000000000..4c79f2b95 --- /dev/null +++ b/Source/zh-Hans.lproj/MainMenu.xib @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/zh-Hans.lproj/preferences.xib b/Source/zh-Hans.lproj/preferences.xib new file mode 100644 index 000000000..b6c6b13e9 --- /dev/null +++ b/Source/zh-Hans.lproj/preferences.xib @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Item 1 + Item 2 + Item 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/zh-Hant.lproj/InfoPlist.strings b/Source/zh-Hant.lproj/InfoPlist.strings index a32867846..83b9178f9 100644 --- a/Source/zh-Hant.lproj/InfoPlist.strings +++ b/Source/zh-Hant.lproj/InfoPlist.strings @@ -1,5 +1,5 @@ -CFBundleName = "小麥注音"; -CFBundleDisplayName = "小麥注音"; +CFBundleName = "威注音"; +CFBundleDisplayName = "威注音"; NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; -"org.openvanilla.inputmethod.McBopomofo.Bopomofo" = "小麥注音"; -"org.openvanilla.inputmethod.McBopomofo.PlainBopomofo" = "傳統注音"; +"org.openvanilla.inputmethod.vChewing.Bopomofo" = "威注音"; +"org.openvanilla.inputmethod.vChewing.PlainBopomofo" = "ㄅ半全注"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index e49af5270..ebfa32d02 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* No comment provided by engineer. */ -"About McBopomofo…" = "關於小麥注音…"; +"About vChewing…" = "關於威注音…"; /* No comment provided by engineer. */ "Clear Learning Dictionary (%ju Items)" = "清除學習辭典 (%ju 個項目)"; @@ -11,7 +11,7 @@ "Enable Selection Learning" = "使用自動學習功能"; /* No comment provided by engineer. */ -"McBopomofo Preferences" = "小麥注音偏好設定"; +"vChewing Preferences" = "威注音偏好設定"; /* No comment provided by engineer. */ "Check Later" = "晚點再通知我"; @@ -23,7 +23,7 @@ "Check for Update Completed" = "新版檢查完畢"; /* No comment provided by engineer. */ -"You are already using the latest version of McBopomofo." = "目前使用的已經是最新版本。"; +"You are already using the latest version of vChewing." = "目前使用的已經是最新版本。"; /* No comment provided by engineer. */ "Update Check Failed" = "無法檢查新版"; @@ -47,6 +47,6 @@ "Visit Website" = "前往網站"; /* No comment provided by engineer. */ -"You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@" = "目前使用的小麥注音版本是 %1$@ (%2$@),網路上有更新版本 %3$@ (%4$@) 可供下載。是否要前往小麥注音網站下載新版來安裝?%5$@"; +"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音版本是 %1$@ (%2$@),網路上有更新版本 %3$@ (%4$@) 可供下載。是否要前往威注音網站下載新版來安裝?%5$@"; "Chinese Conversion" = "輸出簡體中文"; diff --git a/Source/zh-Hant.lproj/MainMenu.xib b/Source/zh-Hant.lproj/MainMenu.xib index 5f7bcf12f..4c79f2b95 100644 --- a/Source/zh-Hant.lproj/MainMenu.xib +++ b/Source/zh-Hant.lproj/MainMenu.xib @@ -14,10 +14,10 @@ - - + + - + @@ -32,7 +32,7 @@ - + diff --git a/Source/zh-Hant.lproj/preferences.xib b/Source/zh-Hant.lproj/preferences.xib index 17e6e2c9a..37397ed02 100644 --- a/Source/zh-Hant.lproj/preferences.xib +++ b/Source/zh-Hant.lproj/preferences.xib @@ -20,7 +20,7 @@ - + @@ -49,7 +49,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -79,7 +79,7 @@ - + @@ -88,7 +88,7 @@ - + @@ -97,7 +97,7 @@ - + @@ -106,10 +106,10 @@ - + - + @@ -181,7 +181,7 @@ + + + + + + + + Item 1 + Item 2 + Item 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + - - - + + - - - + + - + @@ -129,11 +171,10 @@ - - - + + - + @@ -155,9 +196,11 @@ - + - + + + @@ -178,46 +221,11 @@ - - - - - - - - - - Item 1 - Item 2 - Item 3 - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index d0ad6f74f..17b7bb072 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -79,6 +79,7 @@ static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize static NSString *const kDisableUserCandidateSelectionLearning = @"DisableUserCandidateSelectionLearning"; static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey"; static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey"; +static NSString *const kEscToCleanInputBufferKey = @"EscToCleanInputBufferKey"; // advanced (usually optional) settings static NSString *const kCandidateTextFontName = @"CandidateTextFontName"; @@ -742,21 +743,40 @@ public: } // Esc - if (charCode == 27) { - // if reading is not empty, we cancel the reading; Apple's built-in Zhuyin (and the erstwhile Hanin) has a default option that Esc "cancels" the current composed character and revert it to Bopomofo reading, in odds with the expectation of users from other platforms - - if (_bpmfReadingBuffer->isEmpty()) { - // no nee to beep since the event is deliberately triggered by user - - if (![_composingBuffer length]) { - return NO; - } - } - else { - _bpmfReadingBuffer->clear(); - } - - [self updateClientComposingBuffer:client]; + if (charCode == 27) { + BOOL escToClearInputBufferEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kEscToCleanInputBufferKey]; + + if (escToClearInputBufferEnabled) { + // if the optioon is enabled, we clear everythiong including the composing + // buffer, walked nodes and the reading. + if (![_composingBuffer length]) { + return NO; + } + _bpmfReadingBuffer->clear(); + _builder->clear(); + _walkedNodes.clear(); + [_composingBuffer setString:@""]; + } + else { + // if reading is not empty, we cancel the reading; Apple's built-in + // Zhuyin (and the erstwhile Hanin) has a default option that Esc + // "cancels" the current composed character and revert it to + // Bopomofo reading, in odds with the expectation of users from + // other platforms + + if (_bpmfReadingBuffer->isEmpty()) { + // no nee to beep since the event is deliberately triggered by user + + if (![_composingBuffer length]) { + return NO; + } + } + else { + _bpmfReadingBuffer->clear(); + } + } + + [self updateClientComposingBuffer:client]; return YES; } diff --git a/Source/en.lproj/preferences.strings b/Source/en.lproj/preferences.strings new file mode 100644 index 000000000..ff209dfac --- /dev/null +++ b/Source/en.lproj/preferences.strings @@ -0,0 +1,108 @@ + +/* Class = "NSWindow"; title = "Bopomofo Preferences"; ObjectID = "1"; */ +"1.title" = "Bopomofo Preferences"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "5"; */ +"5.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "Standard"; ObjectID = "6"; */ +"6.title" = "Standard"; + +/* Class = "NSMenuItem"; title = "ETen"; ObjectID = "7"; */ +"7.title" = "ETen"; + +/* Class = "NSMenuItem"; title = "Hsu"; ObjectID = "8"; */ +"8.title" = "Hsu"; + +/* Class = "NSMenuItem"; title = "ETen26"; ObjectID = "9"; */ +"9.title" = "ETen26"; + +/* Class = "NSMenuItem"; title = "Hanyu Pinyin"; ObjectID = "10"; */ +"10.title" = "Hanyu Pinyin"; + +/* Class = "NSTextFieldCell"; title = "Bopomofo Keyboard Layout:"; ObjectID = "12"; */ +"12.title" = "Bopomofo Keyboard Layout:"; + +/* Class = "NSTextFieldCell"; title = "Show Candidate Phrase:"; ObjectID = "14"; */ +"14.title" = "Show Candidate Phrase:"; + +/* Class = "NSButtonCell"; title = "Before the cursor (like Hanin)"; ObjectID = "16"; */ +"16.title" = "Before the cursor (like Hanin)"; + +/* Class = "NSButtonCell"; title = "After the cursor (like MS IME)"; ObjectID = "17"; */ +"17.title" = "After the cursor (like MS IME)"; + +/* Class = "NSButtonCell"; title = "Radio"; ObjectID = "18"; */ +"18.title" = "Radio"; + +/* Class = "NSButtonCell"; title = "Radio"; ObjectID = "20"; */ +"20.title" = "Radio"; + +/* Class = "NSButtonCell"; title = "Horizontal"; ObjectID = "21"; */ +"21.title" = "Horizontal"; + +/* Class = "NSButtonCell"; title = "Vertical"; ObjectID = "22"; */ +"22.title" = "Vertical"; + +/* Class = "NSTextFieldCell"; title = "Candidate List Style:"; ObjectID = "24"; */ +"24.title" = "Candidate List Style:"; + +/* Class = "NSTextFieldCell"; title = "Candidate Text Size:"; ObjectID = "29"; */ +"29.title" = "Candidate Text Size:"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "92"; */ +"92.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "12"; ObjectID = "93"; */ +"93.title" = "12"; + +/* Class = "NSMenuItem"; title = "14"; ObjectID = "94"; */ +"94.title" = "14"; + +/* Class = "NSMenuItem"; title = "16"; ObjectID = "95"; */ +"95.title" = "16"; + +/* Class = "NSMenuItem"; title = "18"; ObjectID = "96"; */ +"96.title" = "18"; + +/* Class = "NSMenuItem"; title = "24"; ObjectID = "98"; */ +"98.title" = "24"; + +/* Class = "NSMenuItem"; title = "32"; ObjectID = "99"; */ +"99.title" = "32"; + +/* Class = "NSMenuItem"; title = "64"; ObjectID = "100"; */ +"100.title" = "64"; + +/* Class = "NSMenuItem"; title = "96"; ObjectID = "101"; */ +"101.title" = "96"; + +/* Class = "NSButtonCell"; title = "Space key chooses candidate"; ObjectID = "110"; */ +"110.title" = "Space key chooses candidate"; + +/* Class = "NSTextFieldCell"; title = "Alphanumeric Keyboard Layout:"; ObjectID = "126"; */ +"126.title" = "Alphanumeric Keyboard Layout:"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "128"; */ +"128.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "IBM"; ObjectID = "137"; */ +"137.title" = "IBM"; + +/* Class = "NSTextFieldCell"; title = "Selection Keys:"; ObjectID = "FnD-oH-El5"; */ +"FnD-oH-El5.title" = "Selection Keys:"; + +/* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ +"Z9t-P0-BLF.title" = "Check for updates automatically"; + +/* Class = "NSButtonCell"; title = "ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ +"f2j-xD-4xK.title" = "ESC key clears entire input buffer"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[1] = "Item 2"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[1]" = "Item 2"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; diff --git a/Source/zh-Hans.lproj/preferences.strings b/Source/zh-Hans.lproj/preferences.strings new file mode 100644 index 000000000..90cc93355 --- /dev/null +++ b/Source/zh-Hans.lproj/preferences.strings @@ -0,0 +1,108 @@ + +/* Class = "NSWindow"; title = "Bopomofo Preferences"; ObjectID = "1"; */ +"1.title" = "注音偏好设定"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "5"; */ +"5.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "Standard"; ObjectID = "6"; */ +"6.title" = "大千"; + +/* Class = "NSMenuItem"; title = "ETen"; ObjectID = "7"; */ +"7.title" = "倚天传统"; + +/* Class = "NSMenuItem"; title = "Hsu"; ObjectID = "8"; */ +"8.title" = "许氏"; + +/* Class = "NSMenuItem"; title = "ETen26"; ObjectID = "9"; */ +"9.title" = "倚天二六"; + +/* Class = "NSMenuItem"; title = "Hanyu Pinyin"; ObjectID = "10"; */ +"10.title" = "汉语拼音+数字标调"; + +/* Class = "NSTextFieldCell"; title = "Bopomofo Keyboard Layout:"; ObjectID = "12"; */ +"12.title" = "注音键盘布局:"; + +/* Class = "NSTextFieldCell"; title = "Show Candidate Phrase:"; ObjectID = "14"; */ +"14.title" = "用于显示候选词的游标定位:"; + +/* Class = "NSButtonCell"; title = "Before the cursor (like Hanin)"; ObjectID = "16"; */ +"16.title" = "游标前置(像松下汉音输入法)"; + +/* Class = "NSButtonCell"; title = "After the cursor (like MS IME)"; ObjectID = "17"; */ +"17.title" = "游标后置(像微软新注音)"; + +/* Class = "NSButtonCell"; title = "Radio"; ObjectID = "18"; */ +"18.title" = "Radio"; + +/* Class = "NSButtonCell"; title = "Radio"; ObjectID = "20"; */ +"20.title" = "Radio"; + +/* Class = "NSButtonCell"; title = "Horizontal"; ObjectID = "21"; */ +"21.title" = "横向"; + +/* Class = "NSButtonCell"; title = "Vertical"; ObjectID = "22"; */ +"22.title" = "纵向"; + +/* Class = "NSTextFieldCell"; title = "Candidate List Style:"; ObjectID = "24"; */ +"24.title" = "候选词窗格排版:"; + +/* Class = "NSTextFieldCell"; title = "Candidate Text Size:"; ObjectID = "29"; */ +"29.title" = "候选词窗格字型大小:"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "92"; */ +"92.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "12"; ObjectID = "93"; */ +"93.title" = "12"; + +/* Class = "NSMenuItem"; title = "14"; ObjectID = "94"; */ +"94.title" = "14"; + +/* Class = "NSMenuItem"; title = "16"; ObjectID = "95"; */ +"95.title" = "16"; + +/* Class = "NSMenuItem"; title = "18"; ObjectID = "96"; */ +"96.title" = "18"; + +/* Class = "NSMenuItem"; title = "24"; ObjectID = "98"; */ +"98.title" = "24"; + +/* Class = "NSMenuItem"; title = "32"; ObjectID = "99"; */ +"99.title" = "32"; + +/* Class = "NSMenuItem"; title = "64"; ObjectID = "100"; */ +"100.title" = "64"; + +/* Class = "NSMenuItem"; title = "96"; ObjectID = "101"; */ +"101.title" = "96"; + +/* Class = "NSButtonCell"; title = "Space key chooses candidate"; ObjectID = "110"; */ +"110.title" = "摁空格键以选取候选词"; + +/* Class = "NSTextFieldCell"; title = "Alphanumeric Keyboard Layout:"; ObjectID = "126"; */ +"126.title" = "英数键盘布局:"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "128"; */ +"128.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "IBM"; ObjectID = "137"; */ +"137.title" = "IBM"; + +/* Class = "NSTextFieldCell"; title = "Selection Keys:"; ObjectID = "FnD-oH-El5"; */ +"FnD-oH-El5.title" = "选字键:"; + +/* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ +"Z9t-P0-BLF.title" = "自动检查软件更新"; + +/* Class = "NSButtonCell"; title = "ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ +"f2j-xD-4xK.title" = "敲 ESC 键以清空整个输入缓冲区"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[1] = "Item 2"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[1]" = "Item 2"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; diff --git a/Source/zh-Hans.lproj/preferences.xib b/Source/zh-Hans.lproj/preferences.xib deleted file mode 100644 index b6c6b13e9..000000000 --- a/Source/zh-Hans.lproj/preferences.xib +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + - - - - - - + + + + + + - - - + + + - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + - + - - + - + + + @@ -200,8 +316,20 @@ Gw + + + + + + + + - + + + + + diff --git a/Source/Installer/Installer-Info.plist b/Source/Installer/Installer-Info.plist index 9088d5a86..502ad6fb6 100644 --- a/Source/Installer/Installer-Info.plist +++ b/Source/Installer/Installer-Info.plist @@ -2,6 +2,8 @@ + CFEULAContent + License texts used in the customized about window. CFBundleDevelopmentRegion en CFBundleExecutable diff --git a/Source/Installer/en.lproj/InfoPlist.strings b/Source/Installer/en.lproj/InfoPlist.strings index 6efd7f045..400f8bfd7 100644 --- a/Source/Installer/en.lproj/InfoPlist.strings +++ b/Source/Installer/en.lproj/InfoPlist.strings @@ -1,4 +1,5 @@ /* Localized versions of Info.plist keys */ -CFBundleName = "Install vChewing"; +CFBundleName = "vChewing Installer"; NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; +CFEULAContent = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\n1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\n2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"; diff --git a/Source/Installer/en.lproj/License.rtf b/Source/Installer/en.lproj/License.rtf deleted file mode 100644 index 937871e1b..000000000 --- a/Source/Installer/en.lproj/License.rtf +++ /dev/null @@ -1,22 +0,0 @@ -{\rtf1\ansi\ansicpg950\cocoartf2636 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 SFProText-Bold;\f1\fnil\fcharset0 SFProText-Regular;} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\margl1440\margr1440\vieww16860\viewh12620\viewkind0 -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 - -\f0\b\fs36 \cf0 vChewing License Agreement -\f1\b0\fs24 \ -\ -McBopomofo Engine Copyright (c) 2011-2022 OpenVanilla Project (Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.)\ -vChewing Phrase Database Maintained by Shiki Suen; Special Programming Reinforcements by Hiraku Wang.\ -Unless specially commented, Shiki Suen, the maintainer of the vChewing project, does not own any rights of the programming parts of it.\ -\ -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ -\ -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ -\ -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above.\ -\ -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ -} \ No newline at end of file diff --git a/Source/Installer/en.lproj/Localizable.strings b/Source/Installer/en.lproj/Localizable.strings index d7db1566d..3dd6bbbdf 100644 --- a/Source/Installer/en.lproj/Localizable.strings +++ b/Source/Installer/en.lproj/Localizable.strings @@ -1,28 +1,11 @@ -/* No comment provided by engineer. */ "%@ (for version %@)" = "%1$@ (for version %2$@)"; - -/* No comment provided by engineer. */ -"Agree and Upgrade" = "Agree and Upgrade"; - -/* No comment provided by engineer. */ +"Upgrade" = "Accept-Upgrade"; "Cancel" = "Cancel"; - -/* No comment provided by engineer. */ "Cannot activate the input method." = "Cannot activate the input method."; - -/* No comment provided by engineer. */ "Cannot copy the file to the destination." = "Cannot copy the file to the destination."; - -/* No comment provided by engineer. */ "Install Failed" = "Install Failed"; - -/* No comment provided by engineer. */ "Installation Successful" = "Installation Successful"; - -/* No comment provided by engineer. */ "OK" = "OK"; - -/* No comment provided by engineer. */ "vChewing is ready to use." = "vChewing is ready to use."; "Stopping the old version. This may take up to one minute…" = "Stopping the old version. This may take up to one minute…"; @@ -33,7 +16,6 @@ "Abort" = "Abort"; "Cannot register input source %@ at %@." = "Cannot register input source %@ at %@."; "Cannot find input source %@ after registration." = "Cannot find input source %@ after registration."; - "Warning" = "Warning"; "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources."; "Continue" = "Continue"; diff --git a/Source/Installer/en.lproj/MainMenu.strings b/Source/Installer/en.lproj/MainMenu.strings new file mode 100644 index 000000000..73bd2e320 --- /dev/null +++ b/Source/Installer/en.lproj/MainMenu.strings @@ -0,0 +1,72 @@ + +/* Class = "NSMenu"; title = "AMainMenu"; ObjectID = "29"; */ +"29.title" = "AMainMenu"; + +/* Class = "NSMenuItem"; title = "vChewing Installer"; ObjectID = "56"; */ +"56.title" = "vChewing Installer"; + +/* Class = "NSMenu"; title = "vChewing Installer"; ObjectID = "57"; */ +"57.title" = "vChewing Installer"; + +/* Class = "NSMenuItem"; title = "About vChewing Installer"; ObjectID = "58"; */ +"58.title" = "About vChewing Installer"; + +/* Class = "NSMenuItem"; title = "File"; ObjectID = "83"; */ +"83.title" = "File"; + +/* Class = "NSMenu"; title = "Services"; ObjectID = "130"; */ +"130.title" = "Services"; + +/* Class = "NSMenuItem"; title = "Services"; ObjectID = "131"; */ +"131.title" = "Services"; + +/* Class = "NSMenuItem"; title = "Hide vChewing Installer"; ObjectID = "134"; */ +"134.title" = "Hide vChewing Installer"; + +/* Class = "NSMenuItem"; title = "Quit vChewing Installer"; ObjectID = "136"; */ +"136.title" = "Quit vChewing Installer"; + +/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */ +"145.title" = "Hide Others"; + +/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */ +"150.title" = "Show All"; + +/* Class = "NSWindow"; title = "vChewing Installer"; ObjectID = "371"; */ +"371.title" = "vChewing Installer"; + +/* Class = "NSButtonCell"; title = "I Accept"; ObjectID = "576"; */ +"576.title" = "I Accept"; + +/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "593"; */ +"593.title" = "Cancel"; + +/* Class = "NSTextFieldCell"; title = "MIT-NTL License:"; ObjectID = "AVS-ih-FXM"; */ +"AVS-ih-FXM.title" = "MIT-NTL License:"; + +/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "GNc-8S-1VG"; */ +"GNc-8S-1VG.title" = "vChewing for macOS"; + +/* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */ +// "JRP-At-H9q.title" = "version_placeholder"; + +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "Q9M-ni-kUM"; */ +"Q9M-ni-kUM.title" = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; + +/* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "QYf-Nf-hoi"; */ +"QYf-Nf-hoi.title" = "Derived from OpenVanilla McBopopmofo Project."; + +/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "VW8-s5-Wpn"; */ +"VW8-s5-Wpn.title" = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen."; + +/* Class = "NSTextFieldCell"; title = "Placeholder for showing copyright information."; ObjectID = "eo3-TK-0rB"; */ +// "eo3-TK-0rB.title" = "Placeholder for showing copyright information."; + +/* Class = "NSWindow"; title = "Window"; ObjectID = "gHl-Hx-eQn"; */ +"gHl-Hx-eQn.title" = "Window"; + +/* Class = "NSTextFieldCell"; title = "By installing the software, click the \"I Accept\" to the terms above:"; ObjectID = "mf8-6e-z7X"; */ +"mf8-6e-z7X.title" = "By installing the software, you must accept the terms above."; + +/* Class = "NSTextFieldCell"; title = "Stopping the old version. This may take up to one minute…"; ObjectID = "nTo-dx-qfZ"; */ +"nTo-dx-qfZ.title" = "Stopping the old version. This may take up to one minute…"; diff --git a/Source/Installer/zh-Hans.lproj/InfoPlist.strings b/Source/Installer/zh-Hans.lproj/InfoPlist.strings index 4eeef85e2..a1ea2cf15 100644 --- a/Source/Installer/zh-Hans.lproj/InfoPlist.strings +++ b/Source/Installer/zh-Hans.lproj/InfoPlist.strings @@ -1,4 +1,5 @@ /* Localized versions of Info.plist keys */ -CFBundleName = "安装威注音"; +CFBundleName = "威注音安装程式"; NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; +CFEULAContent = "软件之著作权利人依此麻理授权条款,将其对于软件之著作权利授权释出,只须使用者践履以下二项麻理授权条款叙明之义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用之权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面之应用,而散布程式之人、更可将上述权利传递予其后收受程式之后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款之义务性规定,则其对程式亦享有与前手运用范围相同之同一权利。\n\n甲、散布此一软件程序者,须将本条款其上之「著作权声明」及以下之「免责声明」内嵌于软件程序及其重制作品之实体之中。\n\n乙、敝授权合约不提供对「贡献者」之商品名称、商标、服务标志或产品名称之商标许可,除非用以满足履行上文所述义务之必要。\n\n因麻理软件程序之授权模式乃是无偿提供,是以在现行法律之架构下可以主张合理之免除担保责任。麻理软件之著作权人或任何之后续散布者,对于其所散布之麻理软件程序皆不负任何形式上实质上之担保责任,明示亦或隐喻、商业利用性亦或特定目之使用性,这些均不在保障之列。利用麻理软件程序之所有风险均由使用者自行担负。假如所使用之麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要之服务支出。麻理软件程序之著作权人不负任何形式上实质上之担保责任,无论任何一般之、特殊之、偶发之、因果关系式之损害,或是麻理软件程序之不适用性,均须由使用者自行负担。\n"; diff --git a/Source/Installer/zh-Hans.lproj/License.rtf b/Source/Installer/zh-Hans.lproj/License.rtf deleted file mode 100644 index 00df0415d..000000000 --- a/Source/Installer/zh-Hans.lproj/License.rtf +++ /dev/null @@ -1,50 +0,0 @@ -{\rtf1\ansi\ansicpg950\cocoartf2636 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 SFProText-Bold;\f1\fnil\fcharset0 SFProText-Regular;\f2\fnil\fcharset136 PingFangSC-Regular; -} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\margl1440\margr1440\vieww16860\viewh12620\viewkind0 -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 - -\f0\b\fs36 \cf0 vChewing License Agreement -\f1\b0\fs24 \ -\ -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 - -\f2 \cf0 \'a4\'70\uc0\u40614 \'aa\'60\'ad\'b5\'a4\'de\'c0\'ba\'b5\'db\'a7\'40\u26435 \'a7\'51\'a9\'d2\'a6\'b3 -\f1 \'a9 2011-2022 OpenVanilla -\f2 \uc0\u19987 \'ae\'d7\'a1\'5d -\f1 Mengjuei Hsieh, Lukhnos Liu, Zonble Yang -\f2 \'b5\'a5\'a1\'5e\'a1\'43 -\f1 \ - -\f2 \'ab\'c2\'aa\'60\'ad\'b5\uc0\u35789 \u24211 \'a5\'d1\u23385 \'a7\'d3\u36149 \u32500 \u25252 \'a1\'41\'b7\'50\u35874 -\f1 Hiraku Wang -\f2 \'a6\'62\'b5\'7b\'a6\'a1\'a5\'5c\'af\'e0\uc0\u25193 \'ae\'69\'a4\'e8\'ad\'b1\'aa\'ba\u21327 \'a4\'4f\'a1\'43 -\f1 \ - -\f2 \'b0\'a3\'af\'53\'ae\'ed\uc0\u26631 \'aa\'60\'a4\'a7\u22788 \'a5\'48\'a5\'7e\'a1\'41\'ab\'c2\'aa\'60\'ad\'b5\u36755 \'a4\'4a\'aa\'6b\u32500 \u25252 \'a4\'48\u23385 \'a7\'d3\u36149 \u23545 \u35813 \u20135 \'ab\'7e\'aa\'ba\'b5\'7b\'a7\'c7\'b3\'a1\'a4\'c0\'a4\'a3\'a8\'c9\'a6\'b3\'a5\'f4\'a6\'f3\'a9\'d2\'a6\'b3\u26435 \'a1\'43 -\f1 \ -\ - -\f2 \uc0\u36719 \'a5\'f3\'a4\'a7\'b5\'db\'a7\'40\u26435 \'a7\'51\'a4\'48\'a8\'cc\'a6\'b9\'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\'a1\'41\u23558 \'a8\'e4\u23545 \'a4\'5f\u36719 \'a5\'f3\'a4\'a7\'b5\'db\'a7\'40\u26435 \'a7\'51\'b1\'c2\u26435 \u37322 \'a5\'58\'a1\'41\'a5\'75\u39035 \'a8\'cf\'a5\'ce\'aa\'cc\u36341 \'bc\'69\'a5\'48\'a4\'55\'a4\'47\u39033 \'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\u21465 \'a9\'fa\'a4\'a7\u20041 \u21153 \'a9\'ca\u35268 \'a9\'77\'a1\'41\'a8\'e4\'a7\'59\'a8\'c9\'a6\'b3\u23545 \'a6\'b9\u36719 \'a5\'f3\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ac\'db\u20851 \u35828 \'a9\'fa\'a4\'e5\u26723 \'a6\'db\'a5\'d1\'a4\'a3\'a8\'fc\'ad\'ad\'a8\'ee\'a6\'61\u36827 \'a6\'e6\'a7\'51\'a5\'ce\'a4\'a7\u26435 \'a7\'51\'a1\'41\'ad\'53\u22260 \'a5\'5d\'ac\'41\'a1\'75\'a8\'cf\'a5\'ce\'a1\'42\'ad\'ab\'a8\'ee\'a1\'42\'ad\'d7\'a7\'ef\'a1\'42\'a6\'58\'a6\'7d\'a1\'42\'a5\'58\'aa\'a9\'a1\'42\'b4\'b2\'a5\'ac\'a1\'42\'a6\'41\'b1\'c2\u26435 \'a1\'42\'a4\'ce\u36137 \'b0\'e2\'b5\'7b\'a6\'a1\'ad\'ab\'a8\'ee\'a7\'40\'ab\'7e\'a1\'76\'b5\'a5\u35832 \'a6\'68\'a4\'e8\'ad\'b1\'a4\'a7\u24212 \'a5\'ce\'a1\'41\'a6\'d3\'b4\'b2\'a5\'ac\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a1\'42\'a7\'f3\'a5\'69\u23558 \'a4\'57\'ad\'7a\u26435 \'a7\'51\u20256 \u36882 \'a4\'a9\'a8\'e4\'a6\'5a\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a6\'5a\'a4\'e2\'a1\'41\'ad\'d5\'ad\'59\'a8\'e4\'a6\'5a\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a5\'e7\'aa\'41\'c1\'74\'a5\'48\'a4\'55\'a4\'47\u39033 \'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\'a4\'a7\u20041 \u21153 \'a9\'ca\u35268 \'a9\'77\'a1\'41\u21017 \'a8\'e4\u23545 \'b5\'7b\'a6\'a1\'a5\'e7\'a8\'c9\'a6\'b3\'c9\'4f\'ab\'65\'a4\'e2\u36816 \'a5\'ce\'ad\'53\u22260 \'ac\'db\'a6\'50\'a4\'a7\'a6\'50\'a4\'40\u26435 \'a7\'51\'a1\'43\ -\ -\'a5\'d2\'a1\'42\'b4\'b2\'a5\'ac\'a6\'b9\'a4\'40\uc0\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'cc\'a1\'41\u39035 \u23558 \'a5\'bb\u26465 \'b4\'da\'a8\'e4\'a4\'57\'a4\'a7\'a1\'75\'b5\'db\'a7\'40\u26435 \u22768 \'a9\'fa\'a1\'76\'a4\'ce\'a5\'48\'a4\'55\'a4\'a7\'a1\'75\'a7\'4b\u36131 \u22768 \'a9\'fa\'a1\'76\u20869 \'b4\'4f\'a4\'5f\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'ce\'a8\'e4\'ad\'ab\'a8\'ee\'a7\'40\'ab\'7e\'a4\'a7\u23454 \'ca\'5e\'a4\'a7\'a4\'a4\'a1\'43\ -\ -\'a4\'41\'a1\'42\'b1\'cd\'b1\'c2\uc0\u26435 \'a6\'58\u32422 \'a4\'a3\'b4\'a3\'a8\'d1\u23545 \'a1\'75\u36129 \u29486 \'aa\'cc\'a1\'76\'a4\'a7\'b0\'d3\'ab\'7e\'a6\'57\u31216 \'a1\'42\'b0\'d3\u26631 \'a1\'42\'aa\'41\u21153 \u26631 \'a7\'d3\'a9\'ce\u20135 \'ab\'7e\'a6\'57\u31216 \'a4\'a7\'b0\'d3\u26631 \u35768 \'a5\'69\'a1\'41\'b0\'a3\'ab\'44\'a5\'ce\'a5\'48\u28385 \'a8\'ac\'bc\'69\'a6\'e6\'a4\'57\'a4\'e5\'a9\'d2\'ad\'7a\u20041 \u21153 \'a4\'a7\'a5\'b2\'ad\'6e\'a1\'43\ -\ -\'a6\'5d\'b3\'c2\'b2\'7a\uc0\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'a7\'b1\'c2\u26435 \'bc\'d2\'a6\'a1\'a4\'44\'ac\'4f\u26080 \u20607 \'b4\'a3\'a8\'d1\'a1\'41\'ac\'4f\'a5\'48\'a6\'62\u29616 \'a6\'e6\'aa\'6b\'ab\'df\'a4\'a7\'ac\'5b\'cc\'db\'a4\'55\'a5\'69\'a5\'48\'a5\'44\u24352 \'a6\'58\'b2\'7a\'a4\'a7\'a7\'4b\'b0\'a3\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'43\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'a4\'a7\'b5\'db\'a7\'40\u26435 \'a4\'48\'a9\'ce\'a5\'f4\'a6\'f3\'a4\'a7\'a6\'5a\u32493 \'b4\'b2\'a5\'ac\'aa\'cc\'a1\'41\u23545 \'a4\'5f\'a8\'e4\'a9\'d2\'b4\'b2\'a5\'ac\'a4\'a7\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'ac\'d2\'a4\'a3\u36127 \'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\u23454 \u36136 \'a4\'57\'a4\'a7\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'41\'a9\'fa\'a5\'dc\'a5\'e7\'a9\'ce\u38544 \'b3\'eb\'a1\'42\'b0\'d3\u19994 \'a7\'51\'a5\'ce\'a9\'ca\'a5\'e7\'a9\'ce\'af\'53\'a9\'77\'a5\'d8\'a4\'a7\'a8\'cf\'a5\'ce\'a9\'ca\'a1\'41\u36825 \'a8\'c7\'a7\'a1\'a4\'a3\'a6\'62\'ab\'4f\'bb\'d9\'a4\'a7\'a6\'43\'a1\'43\'a7\'51\'a5\'ce\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'a7\'a9\'d2\'a6\'b3\u39118 \u38505 \'a7\'a1\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\u25285 \u36127 \'a1\'43\'b0\'b2\'a6\'70\'a9\'d2\'a8\'cf\'a5\'ce\'a4\'a7\'b3\'c2\'b2\'7a\'b5\'7b\'a7\'c7\u21457 \'a5\'cd\'af\'ca\'b3\'b4\'a9\'ca\u38382 \u39064 \'a1\'41\'a8\'cf\'a5\'ce\'aa\'cc\'bb\'dd\'a6\'db\'a6\'e6\u25285 \u36127 \'ad\'d7\'a5\'bf\'a1\'42\'a7\'ef\'a5\'bf\'a4\'ce\'a5\'b2\'ad\'6e\'a4\'a7\'aa\'41\u21153 \'a4\'e4\'a5\'58\'a1\'43\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'a7\'b5\'db\'a7\'40\u26435 \'a4\'48\'a4\'a3\u36127 \'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\u23454 \u36136 \'a4\'57\'a4\'a7\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'41\u26080 \u35770 \'a5\'f4\'a6\'f3\'a4\'40\'af\'eb\'a4\'a7\'a1\'42\'af\'53\'ae\'ed\'a4\'a7\'a1\'42\'b0\'b8\u21457 \'a4\'a7\'a1\'42\'a6\'5d\'aa\'47\u20851 \'a8\'74\'a6\'a1\'a4\'a7\u25439 \'ae\'60\'a1\'41\'a9\'ce\'ac\'4f\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'a7\'a4\'a3\'d3\'ec\'a5\'ce\'a9\'ca\'a1\'41\'a7\'a1\u39035 \'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\u36127 \u25285 \'a1\'43 -\f1 \ -\ -McBopomofo Engine Copyright (c) 2011-2022 OpenVanilla Project (Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.)\ -vChewing Phrase Database Maintained by Shiki Suen; Special Programming Reinforcements by Hiraku Wang.\ -Unless specially commented, Shiki Suen, the maintainer of the vChewing project, does not own any rights of the programming parts of it.\ -\ -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'93Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ -\ -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ -\ -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above.\ -\ -THE SOFTWARE IS PROVIDED \'93AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ -} \ No newline at end of file diff --git a/Source/Installer/zh-Hans.lproj/Localizable.strings b/Source/Installer/zh-Hans.lproj/Localizable.strings index e1e271824..44d0cab0c 100644 --- a/Source/Installer/zh-Hans.lproj/Localizable.strings +++ b/Source/Installer/zh-Hans.lproj/Localizable.strings @@ -1,39 +1,19 @@ -/* No comment provided by engineer. */ "%@ (for version %@)" = "%1$@ (%2$@ 版)"; - -/* No comment provided by engineer. */ -"Agree and Upgrade" = "同意并升级"; - -/* No comment provided by engineer. */ +"Upgrade" = "接受并升级"; "Cancel" = "取消"; - -/* No comment provided by engineer. */ "Cannot activate the input method." = "无法启用输入法。"; - -/* No comment provided by engineer. */ "Cannot copy the file to the destination." = "无法将输入法拷贝至目的地。"; - -/* No comment provided by engineer. */ "Install Failed" = "安装失败"; - -/* No comment provided by engineer. */ "Installation Successful" = "安装成功"; - -/* No comment provided by engineer. */ -"OK" = "好"; - -/* No comment provided by engineer. */ +"OK" = "确定"; "vChewing is ready to use." = "威注音输入法安装成功"; - "Finish" = "结束"; "Attention" = "请注意"; "vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing 安装完成,但建议您登出或重新开机,以便顺利使用新版。"; - "Fatal Error" = "安装错误"; "Abort" = "放弃安装"; "Cannot register input source %@ at %@." = "无法从档案位置 %2$@ 安装输入法 \"%1$@\"。"; "Cannot find input source %@ after registration." = "在注册完输入法 \"%@\" 仍然无法找到输入法。"; - "Warning" = "安装不完整"; "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "输入法已经安装好,但可能没有完全启用。请从“系统偏好设定” > “键盘” > “输入方式”分页加入输入法。"; "Continue" = "继续"; diff --git a/Source/Installer/zh-Hans.lproj/MainMenu.strings b/Source/Installer/zh-Hans.lproj/MainMenu.strings new file mode 100644 index 000000000..092f81776 --- /dev/null +++ b/Source/Installer/zh-Hans.lproj/MainMenu.strings @@ -0,0 +1,73 @@ + +/* Class = "NSMenu"; title = "AMainMenu"; ObjectID = "29"; */ +"29.title" = "AMainMenu"; + +/* Class = "NSMenuItem"; title = "vChewing Installer"; ObjectID = "56"; */ +"56.title" = "威注音安装程式"; + +/* Class = "NSMenu"; title = "vChewing Installer"; ObjectID = "57"; */ +"57.title" = "威注音安装程式"; + +/* Class = "NSMenuItem"; title = "About vChewing Installer"; ObjectID = "58"; */ +"58.title" = "关于威注音安装程式"; + +/* Class = "NSMenuItem"; title = "File"; ObjectID = "83"; */ +"83.title" = "档案"; + +/* Class = "NSMenu"; title = "Services"; ObjectID = "130"; */ +"130.title" = "服务"; + +/* Class = "NSMenuItem"; title = "Services"; ObjectID = "131"; */ +"131.title" = "服务"; + +/* Class = "NSMenuItem"; title = "Hide vChewing Installer"; ObjectID = "134"; */ +"134.title" = "隐藏威注音安装程式"; + +/* Class = "NSMenuItem"; title = "Quit vChewing Installer"; ObjectID = "136"; */ +"136.title" = "结束威注音安装程式"; + +/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */ +"145.title" = "隐藏其他程式"; + +/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */ +"150.title" = "显示所有程式"; + +/* Class = "NSWindow"; title = "vChewing Installer"; ObjectID = "371"; */ +"371.title" = "威注音输入法安装程式"; + +/* Class = "NSButtonCell"; title = "I Accept"; ObjectID = "576"; */ +"576.title" = "我接受"; + +/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "593"; */ +"593.title" = "取消安装"; + +/* Class = "NSTextFieldCell"; title = "MIT-NTL License:"; ObjectID = "AVS-ih-FXM"; */ +"AVS-ih-FXM.title" = "麻理去商标授权合约 (MIT-NTL License):"; + +/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "GNc-8S-1VG"; */ +"GNc-8S-1VG.title" = "vChewing for macOS"; + +/* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */ +// "JRP-At-H9q.title" = "version_placeholder"; + +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "Q9M-ni-kUM"; */ +"Q9M-ni-kUM.title" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音词库已依照《中华人民共和国国家安全法》与《中华人民共和国反分裂国家法》做过合规处理。"; + +/* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "QYf-Nf-hoi"; */ +"QYf-Nf-hoi.title" = "该专案由 OpenVanilla 小麦注音专案衍生而来。"; + +/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang. +vChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "VW8-s5-Wpn"; */ +"VW8-s5-Wpn.title" = "小麦注音引擎研发:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang (Zonble), 等。\n威注音 macOS 程式研发协力:Hiraku Wang。\n威注音词库维护:孙志贵 (Shiki Suen)。"; + +/* Class = "NSTextFieldCell"; title = "Placeholder for showing copyright information."; ObjectID = "eo3-TK-0rB"; */ +// "eo3-TK-0rB.title" = "Placeholder for showing copyright information."; + +/* Class = "NSWindow"; title = "Window"; ObjectID = "gHl-Hx-eQn"; */ +"gHl-Hx-eQn.title" = "视窗"; + +/* Class = "NSTextFieldCell"; title = "By installing the software, click the \"I Accept\" to the terms above:"; ObjectID = "mf8-6e-z7X"; */ +"mf8-6e-z7X.title" = "若要安装该软件,请接受上述条款。"; + +/* Class = "NSTextFieldCell"; title = "Stopping the old version. This may take up to one minute…"; ObjectID = "nTo-dx-qfZ"; */ +"nTo-dx-qfZ.title" = "等待旧版完全停用,大约需要一分钟…"; diff --git a/Source/Installer/zh-Hans.lproj/MainMenu.xib b/Source/Installer/zh-Hans.lproj/MainMenu.xib deleted file mode 100644 index ef091d7f2..000000000 --- a/Source/Installer/zh-Hans.lproj/MainMenu.xib +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/Installer/zh-Hant.lproj/InfoPlist.strings b/Source/Installer/zh-Hant.lproj/InfoPlist.strings index abd8551f5..006fab5a6 100644 --- a/Source/Installer/zh-Hant.lproj/InfoPlist.strings +++ b/Source/Installer/zh-Hant.lproj/InfoPlist.strings @@ -1,4 +1,5 @@ /* Localized versions of Info.plist keys */ -CFBundleName = "安裝威注音"; +CFBundleName = "威注音安裝程式"; NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; +CFEULAContent = "軟體之著作權利人依此麻理授權條款,將其對於軟體之著作權利授權釋出,只須使用者踐履以下二項麻理授權條款敘明之義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用之權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面之應用,而散布程式之人、更可將上述權利傳遞予其後收受程式之後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款之義務性規定,則其對程式亦享有與前手運用範圍相同之同一權利。\n\n甲、散布此一軟體程式者,須將本條款其上之「著作權聲明」及以下之「免責聲明」內嵌於軟體程式及其重製作品之實體之中。\n\n乙、敝授權合約不提供對「貢獻者」之商品名稱、商標、服務標誌或產品名稱之商標許可,除非用以滿足履行上文所述義務之必要。\n\n因麻理軟體程式之授權模式乃是無償提供,是以在現行法律之架構下可以主張合理之免除擔保責任。麻理軟體之著作權人或任何之後續散布者,對於其所散布之麻理軟體程式皆不負任何形式上實質上之擔保責任,明示亦或隱喻、商業利用性亦或特定目之使用性,這些均不在保障之列。利用麻理軟體程式之所有風險均由使用者自行擔負。假如所使用之麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要之服務支出。麻理軟體程式之著作權人不負任何形式上實質上之擔保責任,無論任何一般之、特殊之、偶發之、因果關係式之損害,或是麻理軟體程式之不適用性,均須由使用者自行負擔。\n"; diff --git a/Source/Installer/zh-Hant.lproj/License.rtf b/Source/Installer/zh-Hant.lproj/License.rtf deleted file mode 100644 index abd07f50a..000000000 --- a/Source/Installer/zh-Hant.lproj/License.rtf +++ /dev/null @@ -1,51 +0,0 @@ -{\rtf1\ansi\ansicpg950\cocoartf2636 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 SFProText-Bold;\f1\fnil\fcharset0 SFProText-Regular;\f2\fnil\fcharset136 PingFangTC-Regular; -} -{\colortbl;\red255\green255\blue255;} -{\*\expandedcolortbl;;} -\margl1440\margr1440\vieww16860\viewh12620\viewkind0 -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 - -\f0\b\fs36 \cf0 vChewing License Agreement -\f1\b0\fs24 \ -\ -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 - -\f2 \cf0 \'a4\'70\'b3\'c1\'aa\'60\'ad\'b5\'a4\'de\'c0\'ba\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'a9\'d2\'a6\'b3 -\f1 \'a9 2011-2022 OpenVanilla -\f2 \'b1\'4d\'ae\'d7\'a1\'5d -\f1 Mengjuei Hsieh, Lukhnos Liu, Zonble Yang -\f2 \'b5\'a5\'a1\'5e\'a1\'43 -\f1 \ - -\f2 \'ab\'c2\'aa\'60\'ad\'b5\'b5\'fc\'ae\'77\'a5\'d1\'ae\'5d\'a7\'d3\'b6\'51\'ba\'fb\'c5\'40\'a1\'41\'b7\'50\'c1\'c2 -\f1 Hiraku Wang -\f2 \'a6\'62\'b5\'7b\'a6\'a1\'a5\'5c\'af\'e0\'c2\'58\'ae\'69\'a4\'e8\'ad\'b1\'aa\'ba\'a8\'f3\'a4\'4f\'a1\'43 -\f1 \ - -\f2 \'b0\'a3\'af\'53\'ae\'ed\'bc\'d0\'aa\'60\'a4\'a7\'b3\'42\'a5\'48\'a5\'7e\'a1\'41\'ab\'c2\'aa\'60\'ad\'b5\'bf\'e9\'a4\'4a\'aa\'6b\'ba\'fb\'c5\'40\'a4\'48\'ae\'5d\'a7\'d3\'b6\'51\'b9\'ef\'b8\'d3\'b2\'a3\'ab\'7e\'aa\'ba\'b5\'7b\'a7\'c7\'b3\'a1\'a4\'c0\'a4\'a3\'a8\'c9\'a6\'b3\'a5\'f4\'a6\'f3\'a9\'d2\'a6\'b3\'c5\'76\'a1\'43 -\f1 \ -\ - -\f2 \'b3\'6e\'c5\'e9\'a4\'a7\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'a4\'48\'a8\'cc\'a6\'b9\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'a1\'41\'b1\'4e\'a8\'e4\'b9\'ef\'a9\'f3\'b3\'6e\'c5\'e9\'a4\'a7\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'b1\'c2\'c5\'76\'c4\'c0\'a5\'58\'a1\'41\'a5\'75\'b6\'b7\'a8\'cf\'a5\'ce\'aa\'cc\'bd\'ee\'bc\'69\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'b1\'d4\'a9\'fa\'a4\'a7\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'a8\'e4\'a7\'59\'a8\'c9\'a6\'b3\'b9\'ef\'a6\'b9\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ac\'db\'c3\'f6\'bb\'a1\'a9\'fa\'a4\'e5\'c0\'c9\'a6\'db\'a5\'d1\'a4\'a3\'a8\'fc\'ad\'ad\'a8\'ee\'a6\'61\'b6\'69\'a6\'e6\'a7\'51\'a5\'ce\'a4\'a7\'c5\'76\'a7\'51\'a1\'41\'bd\'64\'b3\'f2\'a5\'5d\'ac\'41\'a1\'75\'a8\'cf\'a5\'ce\'a1\'42\'ad\'ab\'bb\'73\'a1\'42\'ad\'d7\'a7\'ef\'a1\'42\'a6\'58\'a8\'d6\'a1\'42\'a5\'58\'aa\'a9\'a1\'42\'b4\'b2\'a5\'ac\'a1\'42\'a6\'41\'b1\'c2\'c5\'76\'a1\'42\'a4\'ce\'b3\'63\'b0\'e2\'b5\'7b\'a6\'a1\'ad\'ab\'bb\'73\'a7\'40\'ab\'7e\'a1\'76\'b5\'a5\'bd\'d1\'a6\'68\'a4\'e8\'ad\'b1\'a4\'a7\'c0\'b3\'a5\'ce\'a1\'41\'a6\'d3\'b4\'b2\'a5\'ac\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a1\'42\'a7\'f3\'a5\'69\'b1\'4e\'a4\'57\'ad\'7a\'c5\'76\'a7\'51\'b6\'c7\'bb\'bc\'a4\'a9\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'ab\'e1\'a4\'e2\'a1\'41\'ad\'d5\'ad\'59\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a5\'e7\'aa\'41\'c1\'74\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'a4\'a7\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'ab\'68\'a8\'e4\'b9\'ef\'b5\'7b\'a6\'a1\'a5\'e7\'a8\'c9\'a6\'b3\'bb\'50\'ab\'65\'a4\'e2\'b9\'42\'a5\'ce\'bd\'64\'b3\'f2\'ac\'db\'a6\'50\'a4\'a7\'a6\'50\'a4\'40\'c5\'76\'a7\'51\'a1\'43\ -\ -\'a5\'d2\'a1\'42\'b4\'b2\'a5\'ac\'a6\'b9\'a4\'40\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'cc\'a1\'41\'b6\'b7\'b1\'4e\'a5\'bb\'b1\'f8\'b4\'da\'a8\'e4\'a4\'57\'a4\'a7\'a1\'75\'b5\'db\'a7\'40\'c5\'76\'c1\'6e\'a9\'fa\'a1\'76\'a4\'ce\'a5\'48\'a4\'55\'a4\'a7\'a1\'75\'a7\'4b\'b3\'64\'c1\'6e\'a9\'fa\'a1\'76\'a4\'ba\'b4\'4f\'a9\'f3\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ad\'ab\'bb\'73\'a7\'40\'ab\'7e\'a4\'a7\'b9\'ea\'c5\'e9\'a4\'a7\'a4\'a4\'a1\'43\ -\ -\'a4\'41\'a1\'42\'b1\'cd\'b1\'c2\'c5\'76\'a6\'58\'ac\'f9\'a4\'a3\'b4\'a3\'a8\'d1\'b9\'ef\'a1\'75\'b0\'5e\'c4\'6d\'aa\'cc\'a1\'76\'a4\'a7\'b0\'d3\'ab\'7e\'a6\'57\'ba\'d9\'a1\'42\'b0\'d3\'bc\'d0\'a1\'42\'aa\'41\'b0\'c8\'bc\'d0\'bb\'78\'a9\'ce\'b2\'a3\'ab\'7e\'a6\'57\'ba\'d9\'a4\'a7\'b0\'d3\'bc\'d0\'b3\'5c\'a5\'69\'a1\'41\'b0\'a3\'ab\'44\'a5\'ce\'a5\'48\'ba\'a1\'a8\'ac\'bc\'69\'a6\'e6\'a4\'57\'a4\'e5\'a9\'d2\'ad\'7a\'b8\'71\'b0\'c8\'a4\'a7\'a5\'b2\'ad\'6e\'a1\'43\ -\ -\'a6\'5d\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'a7\'b1\'c2\'c5\'76\'bc\'d2\'a6\'a1\'a4\'44\'ac\'4f\'b5\'4c\'c0\'76\'b4\'a3\'a8\'d1\'a1\'41\'ac\'4f\'a5\'48\'a6\'62\'b2\'7b\'a6\'e6\'aa\'6b\'ab\'df\'a4\'a7\'ac\'5b\'ba\'63\'a4\'55\'a5\'69\'a5\'48\'a5\'44\'b1\'69\'a6\'58\'b2\'7a\'a4\'a7\'a7\'4b\'b0\'a3\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'43\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'a4\'a7\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a9\'ce\'a5\'f4\'a6\'f3\'a4\'a7\'ab\'e1\'c4\'f2\'b4\'b2\'a5\'ac\'aa\'cc\'a1\'41\'b9\'ef\'a9\'f3\'a8\'e4\'a9\'d2\'b4\'b2\'a5\'ac\'a4\'a7\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'ac\'d2\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'a4\'a7\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'a9\'fa\'a5\'dc\'a5\'e7\'a9\'ce\'c1\'f4\'b3\'eb\'a1\'42\'b0\'d3\'b7\'7e\'a7\'51\'a5\'ce\'a9\'ca\'a5\'e7\'a9\'ce\'af\'53\'a9\'77\'a5\'d8\'a4\'a7\'a8\'cf\'a5\'ce\'a9\'ca\'a1\'41\'b3\'6f\'a8\'c7\'a7\'a1\'a4\'a3\'a6\'62\'ab\'4f\'bb\'d9\'a4\'a7\'a6\'43\'a1\'43\'a7\'51\'a5\'ce\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'a7\'a9\'d2\'a6\'b3\'ad\'b7\'c0\'49\'a7\'a1\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'a1\'43\'b0\'b2\'a6\'70\'a9\'d2\'a8\'cf\'a5\'ce\'a4\'a7\'b3\'c2\'b2\'7a\'b5\'7b\'a6\'a1\'b5\'6f\'a5\'cd\'af\'ca\'b3\'b4\'a9\'ca\'b0\'dd\'c3\'44\'a1\'41\'a8\'cf\'a5\'ce\'aa\'cc\'bb\'dd\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'ad\'d7\'a5\'bf\'a1\'42\'a7\'ef\'a5\'bf\'a4\'ce\'a5\'b2\'ad\'6e\'a4\'a7\'aa\'41\'b0\'c8\'a4\'e4\'a5\'58\'a1\'43\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'a7\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'a4\'a7\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'b5\'4c\'bd\'d7\'a5\'f4\'a6\'f3\'a4\'40\'af\'eb\'a4\'a7\'a1\'42\'af\'53\'ae\'ed\'a4\'a7\'a1\'42\'b0\'b8\'b5\'6f\'a4\'a7\'a1\'42\'a6\'5d\'aa\'47\'c3\'f6\'ab\'59\'a6\'a1\'a4\'a7\'b7\'6c\'ae\'60\'a1\'41\'a9\'ce\'ac\'4f\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'a3\'be\'41\'a5\'ce\'a9\'ca\'a1\'41\'a7\'a1\'b6\'b7\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'ad\'74\'be\'e1\'a1\'43\ -\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 - -\f1 \cf0 \ -McBopomofo Engine Copyright (c) 2011-2022 OpenVanilla Project (Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.)\ -vChewing Phrase Database Maintained by Shiki Suen; Special Programming Reinforcements by Hiraku Wang.\ -Unless specially commented, Shiki Suen, the maintainer of the vChewing project, does not own any rights of the programming parts of it.\ -\ -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'93Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ -\ -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ -\ -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above.\ -\ -THE SOFTWARE IS PROVIDED \'93AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ -} \ No newline at end of file diff --git a/Source/Installer/zh-Hant.lproj/Localizable.strings b/Source/Installer/zh-Hant.lproj/Localizable.strings index 3fd65cbb1..d98df05d0 100644 --- a/Source/Installer/zh-Hant.lproj/Localizable.strings +++ b/Source/Installer/zh-Hant.lproj/Localizable.strings @@ -1,28 +1,11 @@ -/* No comment provided by engineer. */ "%@ (for version %@)" = "%1$@ (%2$@ 版)"; - -/* No comment provided by engineer. */ -"Agree and Upgrade" = "同意並升級"; - -/* No comment provided by engineer. */ +"Upgrade" = "接受並升級"; "Cancel" = "取消"; - -/* No comment provided by engineer. */ "Cannot activate the input method." = "無法啟用輸入法。"; - -/* No comment provided by engineer. */ "Cannot copy the file to the destination." = "無法將輸入法拷貝至目的地。"; - -/* No comment provided by engineer. */ "Install Failed" = "安裝失敗"; - -/* No comment provided by engineer. */ "Installation Successful" = "安裝成功"; - -/* No comment provided by engineer. */ -"OK" = "好"; - -/* No comment provided by engineer. */ +"OK" = "確定"; "vChewing is ready to use." = "威注音輸入法安裝成功"; "Finish" = "結束"; @@ -33,7 +16,6 @@ "Abort" = "放棄安裝"; "Cannot register input source %@ at %@." = "無法從檔案位置 %2$@ 安裝輸入法 \"%1$@\"。"; "Cannot find input source %@ after registration." = "在註冊完輸入法 \"%@\" 仍然無法找到輸入法。"; - "Warning" = "安裝不完整"; "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "輸入法已經安裝好,但可能沒有完全啟用。請從「系統偏好設定」 > 「鍵盤」 > 「輸入方式」分頁加入輸入法。"; "Continue" = "繼續"; diff --git a/Source/Installer/zh-Hant.lproj/MainMenu.strings b/Source/Installer/zh-Hant.lproj/MainMenu.strings new file mode 100644 index 000000000..9b500b053 --- /dev/null +++ b/Source/Installer/zh-Hant.lproj/MainMenu.strings @@ -0,0 +1,73 @@ + +/* Class = "NSMenu"; title = "AMainMenu"; ObjectID = "29"; */ +"29.title" = "AMainMenu"; + +/* Class = "NSMenuItem"; title = "vChewing Installer"; ObjectID = "56"; */ +"56.title" = "威注音安裝程式"; + +/* Class = "NSMenu"; title = "vChewing Installer"; ObjectID = "57"; */ +"57.title" = "威注音安裝程式"; + +/* Class = "NSMenuItem"; title = "About vChewing Installer"; ObjectID = "58"; */ +"58.title" = "關於威注音安裝程式"; + +/* Class = "NSMenuItem"; title = "File"; ObjectID = "83"; */ +"83.title" = "檔案"; + +/* Class = "NSMenu"; title = "Services"; ObjectID = "130"; */ +"130.title" = "服務"; + +/* Class = "NSMenuItem"; title = "Services"; ObjectID = "131"; */ +"131.title" = "服務"; + +/* Class = "NSMenuItem"; title = "Hide vChewing Installer"; ObjectID = "134"; */ +"134.title" = "隱藏威注音安裝程式"; + +/* Class = "NSMenuItem"; title = "Quit vChewing Installer"; ObjectID = "136"; */ +"136.title" = "結束威注音安裝程式"; + +/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */ +"145.title" = "隱藏其他程式"; + +/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */ +"150.title" = "顯示所有程式"; + +/* Class = "NSWindow"; title = "vChewing Installer"; ObjectID = "371"; */ +"371.title" = "威注音輸入法安裝程式"; + +/* Class = "NSButtonCell"; title = "I Accept"; ObjectID = "576"; */ +"576.title" = "我接受"; + +/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "593"; */ +"593.title" = "取消安裝"; + +/* Class = "NSTextFieldCell"; title = "MIT-NTL License:"; ObjectID = "AVS-ih-FXM"; */ +"AVS-ih-FXM.title" = "麻理去商標授權合約 (MIT-NTL License):"; + +/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "GNc-8S-1VG"; */ +"GNc-8S-1VG.title" = "vChewing for macOS"; + +/* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */ +// "JRP-At-H9q.title" = "version_placeholder"; + +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "Q9M-ni-kUM"; */ +"Q9M-ni-kUM.title" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音詞庫已依照《中華人民共和國國家安全法》與《中華人民共和國反分裂國家法》做過合規處理。"; + +/* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "QYf-Nf-hoi"; */ +"QYf-Nf-hoi.title" = "該專案由 OpenVanilla 小麥注音專案衍生而來。"; + +/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang. +vChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "VW8-s5-Wpn"; */ +"VW8-s5-Wpn.title" = "小麥注音引擎研發:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang (Zonble), 等。\n威注音 macOS 程式研發協力:Hiraku Wang。\n威注音詞庫維護:孫志貴 (Shiki Suen)。"; + +/* Class = "NSTextFieldCell"; title = "Placeholder for showing copyright information."; ObjectID = "eo3-TK-0rB"; */ +// "eo3-TK-0rB.title" = "Placeholder for showing copyright information."; + +/* Class = "NSWindow"; title = "Window"; ObjectID = "gHl-Hx-eQn"; */ +"gHl-Hx-eQn.title" = "視窗"; + +/* Class = "NSTextFieldCell"; title = "By installing the software, click the \"I Accept\" to the terms above:"; ObjectID = "mf8-6e-z7X"; */ +"mf8-6e-z7X.title" = "若要安裝該軟體,請接受上述條款。"; + +/* Class = "NSTextFieldCell"; title = "Stopping the old version. This may take up to one minute…"; ObjectID = "nTo-dx-qfZ"; */ +"nTo-dx-qfZ.title" = "等待舊版完全停用,大約需要一分鐘…"; diff --git a/Source/Installer/zh-Hant.lproj/MainMenu.xib b/Source/Installer/zh-Hant.lproj/MainMenu.xib deleted file mode 100644 index 1adabe610..000000000 --- a/Source/Installer/zh-Hant.lproj/MainMenu.xib +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 3e064140b..ae4bbaef5 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 5B000FC3278495AD004F02AC /* SimpBopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 5B000FC1278495AD004F02AC /* SimpBopomofo.tiff */; }; 5B000FC4278495AD004F02AC /* SimpBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 5B000FC2278495AD004F02AC /* SimpBopomofo@2x.tiff */; }; + 5B1958522788A2BF00FAEB14 /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */; }; 5B58E87F278413E7003EA2AD /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC3FB82278492DE0022E99A /* data-chs.txt */; }; @@ -44,7 +45,6 @@ 6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; 6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */; }; 6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; }; - 6ACA41FB15FC1D9000935EF6 /* License.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EC15FC1D9000935EF6 /* License.rtf */; }; 6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */; }; 6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; }; 6ACA41FF15FC1D9000935EF6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41F415FC1D9000935EF6 /* main.m */; }; @@ -76,6 +76,9 @@ 5B000FC1278495AD004F02AC /* SimpBopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = SimpBopomofo.tiff; sourceTree = ""; }; 5B000FC2278495AD004F02AC /* SimpBopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "SimpBopomofo@2x.tiff"; sourceTree = ""; }; 5B054058278787710083EF4A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/preferences.strings; sourceTree = ""; }; + 5B19584E27888F5D00FAEB14 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = ""; }; + 5B19584F27888F5F00FAEB14 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = ""; }; + 5B19585127888F6B00FAEB14 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = ""; }; 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = ""; }; 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; 5B42B64127877D6500BB9B9F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/preferences.strings"; sourceTree = ""; }; @@ -84,9 +87,7 @@ 5B58E880278413EF003EA2AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hans"; path = "zh-Hans.lproj/MITLicense.txt"; sourceTree = ""; }; 5B58E881278413F1003EA2AD /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/MITLicense.txt"; sourceTree = ""; }; 5B9781D32763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 5B9781D42763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = "zh-Hans"; path = "zh-Hans.lproj/License.rtf"; sourceTree = ""; }; 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; - 5B9781D62763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.xib"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D92763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.xib"; sourceTree = ""; }; @@ -208,15 +209,12 @@ 6ACA41E815FC1D9000935EF6 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Source/Installer/AppDelegate.h; sourceTree = SOURCE_ROOT; }; 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Source/Installer/AppDelegate.m; sourceTree = SOURCE_ROOT; }; 6ACA41EB15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - 6ACA41ED15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/License.rtf; sourceTree = ""; }; 6ACA41EF15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Installer-Info.plist"; path = "Source/Installer/Installer-Info.plist"; sourceTree = SOURCE_ROOT; }; 6ACA41F315FC1D9000935EF6 /* Installer-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Installer-Prefix.pch"; path = "Source/Installer/Installer-Prefix.pch"; sourceTree = SOURCE_ROOT; }; 6ACA41F415FC1D9000935EF6 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Source/Installer/main.m; sourceTree = SOURCE_ROOT; }; 6ACA41F515FC1D9000935EF6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; - 6ACA41F615FC1D9000935EF6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = "zh-Hant"; path = "zh-Hant.lproj/License.rtf"; sourceTree = ""; }; 6ACA41F715FC1D9000935EF6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; - 6ACA41F815FC1D9000935EF6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.xib"; sourceTree = ""; }; 6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVNonModalAlertWindowController.h; sourceTree = ""; }; 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OVNonModalAlertWindowController.xib; sourceTree = ""; }; 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OVNonModalAlertWindowController.m; sourceTree = ""; }; @@ -473,7 +471,6 @@ 6ACA41E815FC1D9000935EF6 /* AppDelegate.h */, 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */, 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */, - 6ACA41EC15FC1D9000935EF6 /* License.rtf */, 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */, 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */, 6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */, @@ -609,9 +606,9 @@ files = ( 6ACA420215FC1E5200935EF6 /* vChewing.app in Resources */, 6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */, + 5B1958522788A2BF00FAEB14 /* MITLicense.txt in Resources */, 6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */, 6A225A1F23679F2600F685C6 /* NotarizedArchives in Resources */, - 6ACA41FB15FC1D9000935EF6 /* License.rtf in Resources */, 6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */, 6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */, ); @@ -767,17 +764,6 @@ path = Source/Installer; sourceTree = SOURCE_ROOT; }; - 6ACA41EC15FC1D9000935EF6 /* License.rtf */ = { - isa = PBXVariantGroup; - children = ( - 6ACA41ED15FC1D9000935EF6 /* en */, - 6ACA41F615FC1D9000935EF6 /* zh-Hant */, - 5B9781D42763850700897999 /* zh-Hans */, - ); - name = License.rtf; - path = Source/Installer; - sourceTree = SOURCE_ROOT; - }; 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -792,9 +778,10 @@ 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( - 6ACA41F815FC1D9000935EF6 /* zh-Hant */, 6A15B32421A51F2300B92CD3 /* Base */, - 5B9781D62763850700897999 /* zh-Hans */, + 5B19584E27888F5D00FAEB14 /* zh-Hans */, + 5B19584F27888F5F00FAEB14 /* zh-Hant */, + 5B19585127888F6B00FAEB14 /* en */, ); name = MainMenu.xib; path = Source/Installer; -- Gitee From 7bd01179de56e6b6e39a84e0e5fd1998ab2f4ddb Mon Sep 17 00:00:00 2001 From: Hiraku Date: Mon, 10 Jan 2022 16:23:32 +0800 Subject: [PATCH 019/163] Refs #11 HL Memory Model // dev phase 1: Fixed --- Source/InputMethodController.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index b7e0ca492..13ef44001 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -119,6 +119,7 @@ FastLM gLanguageModelSimpBopomofo; static const int kUserOverrideModelCapacity = 500; static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. vChewing::UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); +vChewing::UserOverrideModel gUserOverrideModelSimpBopomofo(kUserOverrideModelCapacity, kObservedOverrideHalflife); // https://clang-analyzer.llvm.org/faq.html __attribute__((annotate("returns_localized_nsstring"))) @@ -328,14 +329,17 @@ static double FindHighestScore(const vector& nodes, double epsilon) { NSString *newInputMode; Formosa::Gramambular::FastLM *newLanguageModel; + vChewing::UserOverrideModel *newUom; if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) { newInputMode = kSimpBopomofoModeIdentifier; newLanguageModel = &gLanguageModelSimpBopomofo; + newUom = &gUserOverrideModelSimpBopomofo; } else { newInputMode = kBopomofoModeIdentifier; newLanguageModel = &gLanguageModel; + newUom = &gUserOverrideModel; } // Only apply the changes if the value is changed @@ -366,6 +370,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) _builder = new BlockReadingBuilder(_languageModel); _builder->setJoinSeparator("-"); } + _uom = newUom; } } -- Gitee From cd5f486b449cd29897e7545596f2da0b9e751864 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 10 Jan 2022 17:49:20 +0800 Subject: [PATCH 020/163] Header copyright information update to 2022. This only addresses those files modified in vChewing project at this moment. --- Source/AppDelegate.h | 10 +++++++--- Source/AppDelegate.m | 10 +++++++--- Source/CandidateUI/VTHorizontalCandidateController.h | 7 ++++++- Source/CandidateUI/VTHorizontalCandidateController.m | 7 ++++++- Source/CandidateUI/VTHorizontalCandidateView.h | 7 ++++++- Source/CandidateUI/VTHorizontalCandidateView.m | 7 ++++++- Source/InputMethodController.h | 10 +++++++--- Source/InputMethodController.mm | 10 +++++++--- Source/Installer/AppDelegate.h | 7 ++++++- Source/Installer/AppDelegate.m | 7 ++++++- Source/UserOverrideModel.cpp | 3 ++- Source/UserOverrideModel.h | 3 ++- Source/main.m | 10 +++++++--- 13 files changed, 75 insertions(+), 23 deletions(-) diff --git a/Source/AppDelegate.h b/Source/AppDelegate.h index 8825051eb..a2c011088 100644 --- a/Source/AppDelegate.h +++ b/Source/AppDelegate.h @@ -1,11 +1,15 @@ // // AppDelegate.h // -// Copyright (c) 2021 The vChewing Project. +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. // // Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) +// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla +// Weizhong Yang (@zonble) @ OpenVanilla +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing // // Based on the Syrup Project and the Formosana Library // by Lukhnos Liu (@lukhnos). diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index 0f1a0d5e3..3c66fb3b9 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -1,11 +1,15 @@ // // AppDelegate.m // -// Copyright (c) 2021 The vChewing Project. +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. // // Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) +// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla +// Weizhong Yang (@zonble) @ OpenVanilla +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing // // Based on the Syrup Project and the Formosana Library // by Lukhnos Liu (@lukhnos). diff --git a/Source/CandidateUI/VTHorizontalCandidateController.h b/Source/CandidateUI/VTHorizontalCandidateController.h index 23c9da4e1..cd3967550 100644 --- a/Source/CandidateUI/VTHorizontalCandidateController.h +++ b/Source/CandidateUI/VTHorizontalCandidateController.h @@ -1,7 +1,12 @@ // // VTHorizontalCandidateController.h // -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Shiki Suen (@ShikiSuen) @ vChewing // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/CandidateUI/VTHorizontalCandidateController.m b/Source/CandidateUI/VTHorizontalCandidateController.m index d3246dbe1..2c5e47c6c 100644 --- a/Source/CandidateUI/VTHorizontalCandidateController.m +++ b/Source/CandidateUI/VTHorizontalCandidateController.m @@ -1,7 +1,12 @@ // // VTHorizontalCandidateController.m // -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Shiki Suen (@ShikiSuen) @ vChewing // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/CandidateUI/VTHorizontalCandidateView.h b/Source/CandidateUI/VTHorizontalCandidateView.h index 492b7c5c1..f0d35fa85 100644 --- a/Source/CandidateUI/VTHorizontalCandidateView.h +++ b/Source/CandidateUI/VTHorizontalCandidateView.h @@ -1,7 +1,12 @@ // // VTHorizontalCandidateView.h // -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Shiki Suen (@ShikiSuen) @ vChewing // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/CandidateUI/VTHorizontalCandidateView.m b/Source/CandidateUI/VTHorizontalCandidateView.m index 765d0fc1b..528928d05 100644 --- a/Source/CandidateUI/VTHorizontalCandidateView.m +++ b/Source/CandidateUI/VTHorizontalCandidateView.m @@ -1,7 +1,12 @@ // // VTHorizontalCandidateView.m // -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Shiki Suen (@ShikiSuen) @ vChewing // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index c595c7af7..e8d5b103f 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -1,11 +1,15 @@ // // InputMethodController.h // -// Copyright (c) 2021 The vChewing Project. +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. // // Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) +// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla +// Weizhong Yang (@zonble) @ OpenVanilla +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing // // Based on the Syrup Project and the Formosana Library // by Lukhnos Liu (@lukhnos). diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 13ef44001..4606d2722 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1,11 +1,15 @@ // // InputMethodController.m // -// Copyright (c) 2021 The vChewing Project. +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. // // Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) +// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla +// Weizhong Yang (@zonble) @ OpenVanilla +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing // // Based on the Syrup Project and the Formosana Library // by Lukhnos Liu (@lukhnos). diff --git a/Source/Installer/AppDelegate.h b/Source/Installer/AppDelegate.h index d493a01d4..f6a1da403 100644 --- a/Source/Installer/AppDelegate.h +++ b/Source/Installer/AppDelegate.h @@ -1,7 +1,12 @@ // // AppDelegate.h // -// Copyright (c) 2011-2012 The vChewing Project. +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Shiki Suen (@ShikiSuen) @ vChewing // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/Installer/AppDelegate.m b/Source/Installer/AppDelegate.m index dd9441598..7f43d2ef7 100644 --- a/Source/Installer/AppDelegate.m +++ b/Source/Installer/AppDelegate.m @@ -1,7 +1,12 @@ // // AppDelegate.m // -// Copyright (c) 2011-2012 The vChewing Project. +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Shiki Suen (@ShikiSuen) @ vChewing // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/UserOverrideModel.cpp b/Source/UserOverrideModel.cpp index aa0a0b4d8..6c10ebea6 100644 --- a/Source/UserOverrideModel.cpp +++ b/Source/UserOverrideModel.cpp @@ -1,7 +1,8 @@ // // UserOverrideModel.cpp // -// Copyright (c) 2017 The vChewing Project. +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/UserOverrideModel.h b/Source/UserOverrideModel.h index c0f700b42..f582583eb 100644 --- a/Source/UserOverrideModel.h +++ b/Source/UserOverrideModel.h @@ -1,7 +1,8 @@ // // UserOverrideModel.h // -// Copyright (c) 2017 The vChewing Project. +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation diff --git a/Source/main.m b/Source/main.m index 1dd9cdff4..6f1da1603 100644 --- a/Source/main.m +++ b/Source/main.m @@ -1,11 +1,15 @@ // // main.m // -// Copyright (c) 2021 The vChewing Project. +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. // // Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) +// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla +// Weizhong Yang (@zonble) @ OpenVanilla +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing // // Based on the Syrup Project and the Formosana Library // by Lukhnos Liu (@lukhnos). -- Gitee From d97ffb72a85d2ca4561a1c967cee0fa8b5a7ed65 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 10 Jan 2022 17:51:14 +0800 Subject: [PATCH 021/163] =?UTF-8?q?Enforce=20Dark=E2=99=82Mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/vChewing-Info.plist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/vChewing-Info.plist b/Source/vChewing-Info.plist index 513261300..6b3d3fa9c 100644 --- a/Source/vChewing-Info.plist +++ b/Source/vChewing-Info.plist @@ -111,6 +111,10 @@ MainMenu NSPrincipalClass NSApplication + NSRequiresAquaSystemAppearance + No + NSWindowDarkChocolate + Yes TICapsLockLanguageSwitchCapable TISInputSourceID -- Gitee From 720617346de3a0d0445a7d0ad001848138b71912 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 10 Jan 2022 17:49:41 +0800 Subject: [PATCH 022/163] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E5=AD=97=E6=AF=8D?= =?UTF-8?q?=E9=81=B8=E5=AD=97=E6=8C=89=E9=8D=B5=E9=A1=AF=E7=A4=BA=E7=82=BA?= =?UTF-8?q?=E5=A4=A7=E5=AF=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 經測試,該設定不會導致使用者需要摁住 SHIFT 來選字。 --- Source/PreferencesWindowController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/PreferencesWindowController.m b/Source/PreferencesWindowController.m index 0dd1feb93..a7ae14edf 100644 --- a/Source/PreferencesWindowController.m +++ b/Source/PreferencesWindowController.m @@ -97,8 +97,8 @@ static NSString *const kDefaultKeys = @"123456789"; [self.selectionKeyComboBox removeAllItems]; [self.selectionKeyComboBox addItemsWithObjectValues:@[ kDefaultKeys, - @"asdfghjkl", - @"asdfzxcvb" + @"ASDFGHJKL", + @"ASDFZXCVB" ]]; NSString *ckeys = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeys]; -- Gitee From 2935819d4291af6335852420efac540fe7c2ede9 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 10 Jan 2022 17:49:55 +0800 Subject: [PATCH 023/163] =?UTF-8?q?=E5=80=99=E9=81=B8=E7=AA=97=E6=A0=BC?= =?UTF-8?q?=EF=BC=9A=E8=AA=BF=E6=95=B4=E9=A0=90=E8=A8=AD=E5=AD=97=E5=9E=8B?= =?UTF-8?q?=E8=88=87=E5=AD=97=E8=99=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/CandidateUI/VTCandidateController.m | 4 ++-- Source/InputMethodController.mm | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/CandidateUI/VTCandidateController.m b/Source/CandidateUI/VTCandidateController.m index 933d10180..3b5f700a7 100644 --- a/Source/CandidateUI/VTCandidateController.m +++ b/Source/CandidateUI/VTCandidateController.m @@ -47,8 +47,8 @@ if (self) { // populate the default values _keyLabels = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"]; - _keyLabelFont = [NSFont systemFontOfSize:14.0]; - _candidateFont = [NSFont systemFontOfSize:18.0]; + _keyLabelFont = [NSFont monospacedDigitSystemFontOfSize:14.0 weight:NSFontWeightMedium]; + _candidateFont = [NSFont systemFontOfSize:18.0 weight:NSFontWeightRegular]; } return self; } diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 4606d2722..ea8b3b5fd 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -57,7 +57,7 @@ using namespace Formosa::Gramambular; using namespace OpenVanilla; // default, min and max candidate list text size -static const NSInteger kDefaultCandidateListTextSize = 16; +static const NSInteger kDefaultCandidateListTextSize = 18; static const NSInteger kMinKeyLabelSize = 10; static const NSInteger kMinCandidateListTextSize = 12; static const NSInteger kMaxCandidateListTextSize = 196; @@ -1268,8 +1268,8 @@ static double FindHighestScore(const vector& nodes, double epsilon) NSString *klFontName = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeyLabelFontName]; NSString *ckeys = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeys]; - gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont systemFontOfSize:keyLabelSize]; - gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize]; + gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont monospacedDigitSystemFontOfSize:keyLabelSize weight:NSFontWeightMedium]; + gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize weight:NSFontWeightRegular]; NSMutableArray *keyLabels = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", nil]; -- Gitee From 317dcc384870307cb0c2d099d1742fe6201e9310 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 10 Jan 2022 18:27:05 +0800 Subject: [PATCH 024/163] =?UTF-8?q?=E5=80=99=E9=81=B8=E7=AA=97=E6=A0=BC?= =?UTF-8?q?=EF=BC=9A=E7=BE=8E=E5=8C=96=20Voltaire=20=E9=81=B8=E5=AD=97?= =?UTF-8?q?=E7=AA=97=E7=9A=84=E7=BE=8E=E8=A1=93=E9=A2=A8=E6=A0=BC=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 之前的設計的審美是 macOS 10.5 Leopard 時代的,從 macOS 10.7 開始掉渣。由於威注音的最低系統需求早已至 macOS 10.12 Sierra,故盡量利用現代 macOS 系統的 Cocoa 桌面美術特性。 --- .../VTHorizontalCandidateController.m | 28 +++++--- .../CandidateUI/VTHorizontalCandidateView.m | 47 +++++++----- .../VTVerticalCandidateController.m | 71 ++++++++++++++----- .../CandidateUI/VTVerticalKeyLabelStripView.m | 30 +++++--- 4 files changed, 121 insertions(+), 55 deletions(-) diff --git a/Source/CandidateUI/VTHorizontalCandidateController.m b/Source/CandidateUI/VTHorizontalCandidateController.m index 2c5e47c6c..61cf9a1d0 100644 --- a/Source/CandidateUI/VTHorizontalCandidateController.m +++ b/Source/CandidateUI/VTHorizontalCandidateController.m @@ -55,10 +55,12 @@ NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0); NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask; - NSPanel *panel = [[NSPanel alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; + NSWindow *panel = [[NSWindow alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; [panel setLevel:kCGPopUpMenuWindowLevel]; [panel setHasShadow:YES]; - + [panel setOpaque:NO]; + [panel setBackgroundColor:[NSColor clearColor]]; + self = [self initWithWindow:panel]; if (self) { contentRect.origin = NSMakePoint(0.0, 0.0); @@ -67,20 +69,30 @@ _candidateView.action = @selector(candidateViewMouseDidClick:); [[panel contentView] addSubview:_candidateView]; - contentRect.size = NSMakeSize(36.0, 20.0); + contentRect.size = NSMakeSize(16.0, 20.0); _nextPageButton = [[NSButton alloc] initWithFrame:contentRect]; _prevPageButton = [[NSButton alloc] initWithFrame:contentRect]; [_nextPageButton setButtonType:NSMomentaryLightButton]; - [_nextPageButton setBezelStyle:NSSmallSquareBezelStyle]; - [_nextPageButton setTitle:@"»"]; + [_nextPageButton setBezelStyle:NSBezelStyleSmallSquare]; + [_nextPageButton setTitle:@"↓"]; [_nextPageButton setTarget:self]; [_nextPageButton setAction:@selector(pageButtonAction:)]; - + [_nextPageButton setWantsLayer: YES]; + [_nextPageButton.layer setCornerRadius: 3]; + [_nextPageButton.layer setBorderColor: [NSColor clearColor].CGColor]; + [_nextPageButton.layer setBorderWidth: 3]; + [_nextPageButton.layer setBackgroundColor: [NSColor windowBackgroundColor].CGColor]; + [_prevPageButton setButtonType:NSMomentaryLightButton]; - [_prevPageButton setBezelStyle:NSSmallSquareBezelStyle]; - [_prevPageButton setTitle:@"«"]; + [_prevPageButton setBezelStyle:NSBezelStyleSmallSquare]; + [_prevPageButton setTitle:@"↑"]; [_prevPageButton setTarget:self]; [_prevPageButton setAction:@selector(pageButtonAction:)]; + [_prevPageButton setWantsLayer: YES]; + [_prevPageButton.layer setCornerRadius: 3]; + [_prevPageButton.layer setBorderColor: [NSColor clearColor].CGColor]; + [_prevPageButton.layer setBorderWidth: 3]; + [_prevPageButton.layer setBackgroundColor: [NSColor windowBackgroundColor].CGColor]; [[panel contentView] addSubview:_nextPageButton]; [[panel contentView] addSubview:_prevPageButton]; diff --git a/Source/CandidateUI/VTHorizontalCandidateView.m b/Source/CandidateUI/VTHorizontalCandidateView.m index 528928d05..a26752d16 100644 --- a/Source/CandidateUI/VTHorizontalCandidateView.m +++ b/Source/CandidateUI/VTHorizontalCandidateView.m @@ -42,6 +42,11 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } @synthesize action = _action; @synthesize target = _target; +static NSColor *colorFromRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return [NSColor colorWithDeviceRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:(a/255.0f)]; +} + - (void)dealloc { _keyLabels = nil; @@ -74,18 +79,20 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } - (void)setKeyLabelFont:(NSFont *)labelFont candidateFont:(NSFont *)candidateFont { + NSColor *clrCandidateText = colorFromRGBA(233,233,233,255); + NSColor *clrCandidateTextIndex = colorFromRGBA(233,233,233,213); NSMutableParagraphStyle *paraStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; [paraStyle setAlignment:NSCenterTextAlignment]; _keyLabelAttrDict = [NSDictionary dictionaryWithObjectsAndKeys: labelFont, NSFontAttributeName, paraStyle, NSParagraphStyleAttributeName, - [NSColor blackColor], NSForegroundColorAttributeName, + clrCandidateTextIndex, NSForegroundColorAttributeName, nil]; _candidateAttrDict = [NSDictionary dictionaryWithObjectsAndKeys: candidateFont, NSFontAttributeName, paraStyle, NSParagraphStyleAttributeName, - [NSColor textColor], NSForegroundColorAttributeName, + clrCandidateText, NSForegroundColorAttributeName, nil]; CGFloat labelFontSize = [labelFont pointSize]; @@ -120,17 +127,21 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } - (void)drawRect:(NSRect)dirtyRect { - NSColor *backgroundColor = [NSColor controlBackgroundColor]; - NSColor *darkGray = [NSColor colorWithDeviceWhite:0.7 alpha:1.0]; - NSColor *lightGray = [NSColor colorWithDeviceWhite:0.8 alpha:1.0]; + NSColor *clrCandidateSelectedBG = [NSColor alternateSelectedControlColor]; + NSColor *clrCandidateSelectedText = colorFromRGBA(233,233,233,255); + NSColor *clrCandidateWindowBorder = colorFromRGBA(255,255,255,75); + NSColor *clrCandidateWindowBG = colorFromRGBA(28,28,28,255); + NSColor *clrCandidateBG = colorFromRGBA(28,28,28,255); + + [self setWantsLayer: YES]; + [self.layer setBorderColor: [clrCandidateWindowBorder CGColor]]; + [self.layer setBorderWidth: 1]; + [self.layer setCornerRadius: 6]; NSRect bounds = [self bounds]; - [backgroundColor setFill]; + [clrCandidateWindowBG setFill]; [NSBezierPath fillRect:bounds]; - - [[NSColor darkGrayColor] setStroke]; - [NSBezierPath strokeLineFromPoint:NSMakePoint(bounds.size.width, 0.0) toPoint:NSMakePoint(bounds.size.width, bounds.size.height)]; NSUInteger count = [_elementWidths count]; CGFloat accuWidth = 0.0; @@ -138,27 +149,26 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } for (NSUInteger index = 0; index < count; index++) { NSDictionary *activeCandidateAttr = _candidateAttrDict; CGFloat currentWidth = [[_elementWidths objectAtIndex:index] doubleValue]; - NSRect labelRect = NSMakeRect(accuWidth, 0.0, currentWidth, _keyLabelHeight); - NSRect candidateRect = NSMakeRect(accuWidth, _keyLabelHeight + 1.0, currentWidth, _candidateTextHeight); + NSRect labelRect = NSMakeRect(accuWidth, 0.0, currentWidth + 1.0, _keyLabelHeight + 1.0); + NSRect candidateRect = NSMakeRect(accuWidth, _keyLabelHeight + 1.0, currentWidth + 1.0, _candidateTextHeight); if (index == _highlightedIndex) { - [darkGray setFill]; + [clrCandidateSelectedBG setFill]; } else { - [lightGray setFill]; + [clrCandidateBG setFill]; } [NSBezierPath fillRect:labelRect]; [[_keyLabels objectAtIndex:index] drawInRect:labelRect withAttributes:_keyLabelAttrDict]; if (index == _highlightedIndex) { - [[NSColor selectedTextBackgroundColor] setFill]; - + [clrCandidateSelectedBG setFill]; activeCandidateAttr = [_candidateAttrDict mutableCopy]; - [(NSMutableDictionary *)activeCandidateAttr setObject:[NSColor selectedTextColor] forKey:NSForegroundColorAttributeName]; + [(NSMutableDictionary *)activeCandidateAttr setObject:clrCandidateSelectedText forKey:NSForegroundColorAttributeName]; } else { - [backgroundColor setFill]; + [clrCandidateBG setFill]; } [NSBezierPath fillRect:candidateRect]; @@ -186,8 +196,7 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } result = index; break; } - - accuWidth += currentWidth + 1.0; + accuWidth += currentWidth; } return result; diff --git a/Source/CandidateUI/VTVerticalCandidateController.m b/Source/CandidateUI/VTVerticalCandidateController.m index 68c340b5d..ba4c32399 100644 --- a/Source/CandidateUI/VTVerticalCandidateController.m +++ b/Source/CandidateUI/VTVerticalCandidateController.m @@ -1,7 +1,12 @@ // // VTVerticalCandidateController.m // -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Shiki Suen (ShikiSuen) @ vChewing // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -36,7 +41,12 @@ NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } static const CGFloat kCandidateTextPadding = 24.0; static const CGFloat kCandidateTextLeftMargin = 8.0; -#if defined(__MAC_10_16) +static NSColor *colorFromRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return [NSColor colorWithDeviceRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:(a/255.0f)]; +} + +#if defined(__MAC_11_0) static const CGFloat kCandidateTextPaddingWithMandatedTableViewPadding = 18.0; static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; #endif @@ -60,29 +70,50 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; - (id)init { - NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0); - NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask; - - NSPanel *panel = [[NSPanel alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - [panel setLevel:kCGPopUpMenuWindowLevel]; + // NSColor *clrCandidateSelectedBG = [NSColor systemBlueColor]; + NSColor *clrCandidateSelectedText = [[NSColor whiteColor] colorWithAlphaComponent: 0.8]; + NSColor *clrCandidateWindowBorder = colorFromRGBA(255,255,255,75); + NSColor *clrCandidateWindowBG = colorFromRGBA(28,28,28,255); + // NSColor *clrCandidateBG = colorFromRGBA(28,28,28,255); + + NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0); + NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask; + NSView *panelView = [[NSView alloc] initWithFrame:contentRect]; + NSWindow *panel = [[NSWindow alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; + [panel setLevel:kCGPopUpMenuWindowLevel]; + [panel setContentView: panelView]; [panel setHasShadow:YES]; - - self = [self initWithWindow:panel]; + [panel setOpaque:NO]; + [panel setBackgroundColor: [NSColor clearColor]]; + [panel setOpaque:false]; + [panelView setWantsLayer: YES]; + [panelView.layer setBorderColor: [clrCandidateWindowBorder CGColor]]; + [panelView.layer setBorderWidth: 1]; + [panelView.layer setCornerRadius: 6]; + [panelView.layer setBackgroundColor: [clrCandidateWindowBG CGColor]]; + + self = [self initWithWindow:panel]; if (self) { contentRect.origin = NSMakePoint(0.0, 0.0); NSRect stripRect = contentRect; stripRect.size.width = 10.0; _keyLabelStripView = [[VTVerticalKeyLabelStripView alloc] initWithFrame:stripRect]; + [_keyLabelStripView setWantsLayer: YES]; + [_keyLabelStripView.layer setBorderWidth: 0]; [[panel contentView] addSubview:_keyLabelStripView]; NSRect scrollViewRect = contentRect; scrollViewRect.origin.x = stripRect.size.width; - scrollViewRect.size.width -= stripRect.size.width; + scrollViewRect.size.width -= stripRect.size.width; _scrollView = [[NSScrollView alloc] initWithFrame:scrollViewRect]; - + [_scrollView setAutohidesScrollers: YES]; + [_scrollView setWantsLayer: YES]; + [_scrollView.layer setBorderWidth: 0]; + [_scrollView setDrawsBackground:NO]; + // >=10.7 only, elastic scroll causes some drawing issues with visible scroller, so we disable it if ([_scrollView respondsToSelector:@selector(setVerticalScrollElasticity:)]) { [_scrollView setVerticalScrollElasticity:NSScrollElasticityNone]; @@ -95,6 +126,8 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"candidate"]; [column setDataCell:[[NSTextFieldCell alloc] init]]; [column setEditable:NO]; + [column.dataCell setTextColor: clrCandidateSelectedText]; + // [column.dataCell setSelectionColor: clrCandidateSelectedBG]; _candidateTextPadding = kCandidateTextPadding; _candidateTextLeftMargin = kCandidateTextLeftMargin; @@ -106,9 +139,11 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; [_tableView setAllowsEmptySelection:YES]; [_tableView setDoubleAction:@selector(rowDoubleClicked:)]; [_tableView setTarget:self]; + [_tableView setBackgroundColor:[NSColor clearColor]]; + [_tableView setGridColor:[NSColor clearColor]]; - #if defined(__MAC_10_16) - if (@available(macOS 10.16, *)) { + #if defined(__MAC_11_0) + if (@available(macOS 11.0, *)) { [_tableView setStyle:NSTableViewStyleFullWidth]; _candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding; _candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding; @@ -394,8 +429,8 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; NSScroller *verticalScroller = [_scrollView verticalScroller]; [verticalScroller setControlSize:controlSize]; - [verticalScroller setScrollerStyle:NSScrollerStyleLegacy]; - scrollerWidth = [NSScroller scrollerWidthForControlSize:controlSize scrollerStyle:NSScrollerStyleLegacy]; + [verticalScroller setScrollerStyle:NSScrollerStyleOverlay]; + scrollerWidth = [NSScroller scrollerWidthForControlSize:controlSize scrollerStyle:NSScrollerStyleOverlay]; } _keyLabelStripView.keyLabelFont = _keyLabelFont; @@ -416,9 +451,9 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; } CGFloat rowSpacing = [_tableView intercellSpacing].height; - CGFloat stripWidth = ceil(maxKeyLabelWidth * 1.20); + CGFloat stripWidth = ceil(maxKeyLabelWidth); CGFloat tableViewStartWidth = ceil(_maxCandidateAttrStringWidth + scrollerWidth);; - CGFloat windowWidth = stripWidth + 1.0 + tableViewStartWidth; + CGFloat windowWidth = stripWidth + tableViewStartWidth; CGFloat windowHeight = keyLabelCount * (rowHeight + rowSpacing); NSRect frameRect = [[self window] frame]; @@ -428,7 +463,7 @@ static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height); [_keyLabelStripView setFrame:NSMakeRect(0.0, 0.0, stripWidth, windowHeight)]; - [_scrollView setFrame:NSMakeRect(stripWidth + 1.0, 0.0, tableViewStartWidth, windowHeight)]; + [_scrollView setFrame:NSMakeRect(stripWidth, 0.0, tableViewStartWidth, windowHeight)]; [[self window] setFrame:frameRect display:NO]; } @end diff --git a/Source/CandidateUI/VTVerticalKeyLabelStripView.m b/Source/CandidateUI/VTVerticalKeyLabelStripView.m index 1770d9f8f..7469dc546 100644 --- a/Source/CandidateUI/VTVerticalKeyLabelStripView.m +++ b/Source/CandidateUI/VTVerticalKeyLabelStripView.m @@ -1,7 +1,12 @@ // // VTVerticalKeyLabelStripView.h // -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Shiki Suen (ShikiSuen) @ vChewing // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation @@ -33,6 +38,11 @@ @synthesize keyLabels = _keyLabels; @synthesize highlightedIndex = _highlightedIndex; +static NSColor *colorFromRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return [NSColor colorWithDeviceRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:(a/255.0f)]; +} + - (void)dealloc { _keyLabelFont = nil; @@ -57,7 +67,7 @@ - (void)drawRect:(NSRect)dirtyRect { NSRect bounds = [self bounds]; - [[NSColor whiteColor] setFill]; + [[NSColor clearColor] setFill]; [NSBezierPath fillRect:bounds]; NSUInteger count = [_keyLabels count]; @@ -66,22 +76,22 @@ } CGFloat cellHeight = bounds.size.height / count; - NSColor *black = [NSColor blackColor]; - NSColor *darkGray = [NSColor colorWithDeviceWhite:0.7 alpha:1.0]; - NSColor *lightGray = [NSColor colorWithDeviceWhite:0.8 alpha:1.0]; - + NSColor *clrCandidateBG = colorFromRGBA(28,28,28,255); + NSColor *clrCandidateTextIndex = colorFromRGBA(233,233,233,213); + NSColor *clrCandidateSelectedBG = [NSColor alternateSelectedControlColor]; + NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; [style setAlignment:NSCenterTextAlignment]; NSDictionary *textAttr = [NSDictionary dictionaryWithObjectsAndKeys: _keyLabelFont, NSFontAttributeName, - black, NSForegroundColorAttributeName, + clrCandidateTextIndex, NSForegroundColorAttributeName, style, NSParagraphStyleAttributeName, nil]; for (NSUInteger index = 0; index < count; index++) { NSRect textRect = NSMakeRect(0.0, index * cellHeight + _labelOffsetY, bounds.size.width, cellHeight - _labelOffsetY); - NSRect cellRect = NSMakeRect(0.0, index * cellHeight, bounds.size.width, cellHeight - 1); + NSRect cellRect = NSMakeRect(0.0, index * cellHeight, bounds.size.width, cellHeight); // fill in the last cell if (index + 1 >= count) { @@ -89,10 +99,10 @@ } if (index == _highlightedIndex) { - [darkGray setFill]; + [clrCandidateSelectedBG setFill]; } else { - [lightGray setFill]; + [clrCandidateBG setFill]; } [NSBezierPath fillRect:cellRect]; -- Gitee From b04d4e34470c64594afb0f3bc52009cdf1e1b6b3 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 10 Jan 2022 19:06:30 +0800 Subject: [PATCH 025/163] Rebrand names of Language Models Revert "Rebrand names of Language Models" This reverts commit 1a2436ae5a6b669840686e84871328d3ef39da4d. Rebrand names of Language Models --- Source/InputMethodController.mm | 130 ++++++++++++++++---------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index ea8b3b5fd..a239b0d4b 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -117,13 +117,13 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; #endif // shared language model object that stores our phrase-term probability database -FastLM gLanguageModel; -FastLM gLanguageModelSimpBopomofo; +FastLM gLanguageModelCHT; +FastLM gLanguageModelCHS; static const int kUserOverrideModelCapacity = 500; static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. -vChewing::UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); -vChewing::UserOverrideModel gUserOverrideModelSimpBopomofo(kUserOverrideModelCapacity, kObservedOverrideHalflife); +vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife); +vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife); // https://clang-analyzer.llvm.org/faq.html __attribute__((annotate("returns_localized_nsstring"))) @@ -159,14 +159,14 @@ public: static const double kEpsilon = 0.000001; static double FindHighestScore(const vector& nodes, double epsilon) { - double highestScore = 0.0; - for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { - double score = ni->node->highestUnigramScore(); - if (score > highestScore) { - highestScore = score; - } - } - return highestScore + epsilon; + double highestScore = 0.0; + for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { + double score = ni->node->highestUnigramScore(); + if (score > highestScore) { + highestScore = score; + } + } + return highestScore + epsilon; } @implementation vChewingInputMethodController @@ -199,9 +199,9 @@ static double FindHighestScore(const vector& nodes, double epsilon) _bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout()); // create the lattice builder - _languageModel = &gLanguageModel; + _languageModel = &gLanguageModelCHT; _builder = new BlockReadingBuilder(_languageModel); - _uom = &gUserOverrideModel; + _uom = &gUserOverrideModelCHT; // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -337,13 +337,13 @@ static double FindHighestScore(const vector& nodes, double epsilon) if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) { newInputMode = kSimpBopomofoModeIdentifier; - newLanguageModel = &gLanguageModelSimpBopomofo; - newUom = &gUserOverrideModelSimpBopomofo; + newLanguageModel = &gLanguageModelCHS; + newUom = &gUserOverrideModelCHS; } else { newInputMode = kBopomofoModeIdentifier; - newLanguageModel = &gLanguageModel; - newUom = &gUserOverrideModel; + newLanguageModel = &gLanguageModelCHT; + newUom = &gUserOverrideModelCHT; } // Only apply the changes if the value is changed @@ -692,15 +692,15 @@ static double FindHighestScore(const vector& nodes, double epsilon) // then walk the lattice [self popOverflowComposingTextAndWalk:client]; - // get user override model suggestion - string overrideValue = - _uom->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); - if (!overrideValue.empty()) { - size_t cursorIndex = [self actualCandidateCursorIndex]; - vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); - double highestScore = FindHighestScore(nodes, kEpsilon); - _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, highestScore); - } + // get user override model suggestion + string overrideValue = + _uom->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + if (!overrideValue.empty()) { + size_t cursorIndex = [self actualCandidateCursorIndex]; + vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); + double highestScore = FindHighestScore(nodes, kEpsilon); + _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, highestScore); + } // then update the text _bpmfReadingBuffer->clear(); @@ -734,40 +734,40 @@ static double FindHighestScore(const vector& nodes, double epsilon) } // Esc - if (charCode == 27) { - BOOL escToClearInputBufferEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kEscToCleanInputBufferKey]; - - if (escToClearInputBufferEnabled) { - // if the optioon is enabled, we clear everythiong including the composing - // buffer, walked nodes and the reading. - if (![_composingBuffer length]) { - return NO; - } - _bpmfReadingBuffer->clear(); - _builder->clear(); - _walkedNodes.clear(); - [_composingBuffer setString:@""]; - } - else { - // if reading is not empty, we cancel the reading; Apple's built-in - // Zhuyin (and the erstwhile Hanin) has a default option that Esc - // "cancels" the current composed character and revert it to - // Bopomofo reading, in odds with the expectation of users from - // other platforms - - if (_bpmfReadingBuffer->isEmpty()) { - // no nee to beep since the event is deliberately triggered by user - - if (![_composingBuffer length]) { - return NO; - } - } - else { - _bpmfReadingBuffer->clear(); - } - } - - [self updateClientComposingBuffer:client]; + if (charCode == 27) { + BOOL escToClearInputBufferEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kEscToCleanInputBufferKey]; + + if (escToClearInputBufferEnabled) { + // if the optioon is enabled, we clear everythiong including the composing + // buffer, walked nodes and the reading. + if (![_composingBuffer length]) { + return NO; + } + _bpmfReadingBuffer->clear(); + _builder->clear(); + _walkedNodes.clear(); + [_composingBuffer setString:@""]; + } + else { + // if reading is not empty, we cancel the reading; Apple's built-in + // Zhuyin (and the erstwhile Hanin) has a default option that Esc + // "cancels" the current composed character and revert it to + // Bopomofo reading, in odds with the expectation of users from + // other platforms + + if (_bpmfReadingBuffer->isEmpty()) { + // no nee to beep since the event is deliberately triggered by user + + if (![_composingBuffer length]) { + return NO; + } + } + else { + _bpmfReadingBuffer->clear(); + } + } + + [self updateClientComposingBuffer:client]; return YES; } @@ -1366,8 +1366,8 @@ static double FindHighestScore(const vector& nodes, double epsilon) size_t cursorIndex = [self actualCandidateCursorIndex]; _builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue); - - _uom->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); + + _uom->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); [_candidates removeAllObjects]; @@ -1407,6 +1407,6 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM & void LTLoadLanguageModel() { - LTLoadLanguageModelFile(@"data", gLanguageModel); - LTLoadLanguageModelFile(@"data-chs", gLanguageModelSimpBopomofo); + LTLoadLanguageModelFile(@"data", gLanguageModelCHT); + LTLoadLanguageModelFile(@"data-chs", gLanguageModelCHS); } -- Gitee From 66ea56c22d102ea7525eb72d7e2da97e3c3372bb Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 11 Jan 2022 12:22:39 +0800 Subject: [PATCH 026/163] Tweaks to the About window and the Installer Window. - Changed the description used in the DISCLAIMER. However, this won't change the fact that everything in the vChewing official phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC. - Window constraint tweaks to unify the design of the two windows. --- Source/Base.lproj/frmAboutWindow.xib | 99 ++++++------- Source/Installer/Base.lproj/MainMenu.xib | 138 ++++++++---------- Source/Installer/en.lproj/Localizable.strings | 2 +- Source/Installer/en.lproj/MainMenu.strings | 4 +- .../Installer/zh-Hans.lproj/MainMenu.strings | 4 +- .../Installer/zh-Hant.lproj/MainMenu.strings | 4 +- Source/en.lproj/frmAboutWindow.strings | 4 +- Source/zh-Hans.lproj/Localizable.strings | 2 +- Source/zh-Hans.lproj/frmAboutWindow.strings | 4 +- Source/zh-Hant.lproj/Localizable.strings | 2 +- Source/zh-Hant.lproj/frmAboutWindow.strings | 4 +- 11 files changed, 122 insertions(+), 145 deletions(-) diff --git a/Source/Base.lproj/frmAboutWindow.xib b/Source/Base.lproj/frmAboutWindow.xib index 46211cec9..c2834bf05 100644 --- a/Source/Base.lproj/frmAboutWindow.xib +++ b/Source/Base.lproj/frmAboutWindow.xib @@ -19,7 +19,7 @@ - + @@ -63,9 +63,6 @@ - - - @@ -74,9 +71,6 @@ - - - @@ -86,7 +80,7 @@ - + @@ -96,9 +90,6 @@ - - - McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al. @@ -119,10 +110,10 @@ vChewing Phrase Database Maintained by Shiki Suen. - - + + + + - DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC. + DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database. @@ -147,16 +141,14 @@ DQ - - - + - + @@ -166,40 +158,39 @@ DQ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Installer/Base.lproj/MainMenu.xib b/Source/Installer/Base.lproj/MainMenu.xib index ff279a715..afe8440c6 100644 --- a/Source/Installer/Base.lproj/MainMenu.xib +++ b/Source/Installer/Base.lproj/MainMenu.xib @@ -67,17 +67,17 @@ - + - + - + @@ -123,10 +123,7 @@ - - - - + @@ -134,10 +131,7 @@ - - - - + @@ -145,9 +139,9 @@ - + - + @@ -156,10 +150,7 @@ - - - - + McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al. @@ -169,10 +160,10 @@ vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database - + - + @@ -180,28 +171,28 @@ vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database - + + + + - DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC. + DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database. - + - - - + - + - - + @@ -210,10 +201,7 @@ vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database - - - - + @@ -221,7 +209,7 @@ vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -296,7 +282,7 @@ Gw - + diff --git a/Source/Installer/en.lproj/Localizable.strings b/Source/Installer/en.lproj/Localizable.strings index 3dd6bbbdf..e10bff264 100644 --- a/Source/Installer/en.lproj/Localizable.strings +++ b/Source/Installer/en.lproj/Localizable.strings @@ -1,5 +1,5 @@ "%@ (for version %@)" = "%1$@ (for version %2$@)"; -"Upgrade" = "Accept-Upgrade"; +"Upgrade" = "Accept & Upgrade"; "Cancel" = "Cancel"; "Cannot activate the input method." = "Cannot activate the input method."; "Cannot copy the file to the destination." = "Cannot copy the file to the destination."; diff --git a/Source/Installer/en.lproj/MainMenu.strings b/Source/Installer/en.lproj/MainMenu.strings index 73bd2e320..e56fce4e5 100644 --- a/Source/Installer/en.lproj/MainMenu.strings +++ b/Source/Installer/en.lproj/MainMenu.strings @@ -50,8 +50,8 @@ /* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */ // "JRP-At-H9q.title" = "version_placeholder"; -/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "Q9M-ni-kUM"; */ -"Q9M-ni-kUM.title" = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; ObjectID = "Q9M-ni-kUM"; */ +"Q9M-ni-kUM.title" = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; /* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "QYf-Nf-hoi"; */ "QYf-Nf-hoi.title" = "Derived from OpenVanilla McBopopmofo Project."; diff --git a/Source/Installer/zh-Hans.lproj/MainMenu.strings b/Source/Installer/zh-Hans.lproj/MainMenu.strings index 092f81776..61ace68fc 100644 --- a/Source/Installer/zh-Hans.lproj/MainMenu.strings +++ b/Source/Installer/zh-Hans.lproj/MainMenu.strings @@ -50,8 +50,8 @@ /* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */ // "JRP-At-H9q.title" = "version_placeholder"; -/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "Q9M-ni-kUM"; */ -"Q9M-ni-kUM.title" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音词库已依照《中华人民共和国国家安全法》与《中华人民共和国反分裂国家法》做过合规处理。"; +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; ObjectID = "Q9M-ni-kUM"; */ +"Q9M-ni-kUM.title" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音官方词库已清除任何「会在法理上妨碍威注音在全球传播」的「与地缘政治及政治意识形态有关的」內容。"; /* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "QYf-Nf-hoi"; */ "QYf-Nf-hoi.title" = "该专案由 OpenVanilla 小麦注音专案衍生而来。"; diff --git a/Source/Installer/zh-Hant.lproj/MainMenu.strings b/Source/Installer/zh-Hant.lproj/MainMenu.strings index 9b500b053..f191da8db 100644 --- a/Source/Installer/zh-Hant.lproj/MainMenu.strings +++ b/Source/Installer/zh-Hant.lproj/MainMenu.strings @@ -50,8 +50,8 @@ /* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */ // "JRP-At-H9q.title" = "version_placeholder"; -/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "Q9M-ni-kUM"; */ -"Q9M-ni-kUM.title" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音詞庫已依照《中華人民共和國國家安全法》與《中華人民共和國反分裂國家法》做過合規處理。"; +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; ObjectID = "Q9M-ni-kUM"; */ +"Q9M-ni-kUM.title" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音官方詞庫已清除任何「會在法理上妨礙威注音在全球傳播」的「與地緣政治及政治意識形態有關的」內容。"; /* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "QYf-Nf-hoi"; */ "QYf-Nf-hoi.title" = "該專案由 OpenVanilla 小麥注音專案衍生而來。"; diff --git a/Source/en.lproj/frmAboutWindow.strings b/Source/en.lproj/frmAboutWindow.strings index c5e24c05b..ae2300518 100644 --- a/Source/en.lproj/frmAboutWindow.strings +++ b/Source/en.lproj/frmAboutWindow.strings @@ -17,8 +17,8 @@ /* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "lblAppTitle"; */ "lblAppTitle.title" = "vChewing for macOS"; -/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "lblDisclaimer"; */ -"lblDisclaimer.title" = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; ObjectID = "lblDisclaimer"; */ +"lblDisclaimer.title" = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; /* Class = "NSTextFieldCell"; title = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; ObjectID = "lblCopyright"; */ // "lblCopyright.title" = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 30ad38bf7..52421b076 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -15,7 +15,7 @@ "New Version Available" = "有新版可下载"; "Not Now" = "以后再说"; "Visit Website" = "前往网站"; -"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音版本是 %1$@ (%2$@),网路上有更新版本 %3$@ (%4$@) 可供下载。是否要前往威注音网站下载新版来安装?%5$@"; +"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),网路上有更新版本 %3$@ (%4$@) 可供下载。是否要前往威注音网站下载新版来安装?%5$@"; "Chinese Conversion" = "优先使用康熙繁体字"; "NotificationSwitchON" = " 已启用"; "NotificationSwitchOFF" = " 已停用"; diff --git a/Source/zh-Hans.lproj/frmAboutWindow.strings b/Source/zh-Hans.lproj/frmAboutWindow.strings index 34b9c0fb7..2da43d0bc 100644 --- a/Source/zh-Hans.lproj/frmAboutWindow.strings +++ b/Source/zh-Hans.lproj/frmAboutWindow.strings @@ -17,8 +17,8 @@ /* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "lblAppTitle"; */ "lblAppTitle.title" = "vChewing for macOS"; -/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "lblDisclaimer"; */ -"lblDisclaimer.title" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音词库已依照《中华人民共和国国家安全法》与《中华人民共和国反分裂国家法》做过合规处理。"; +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; ObjectID = "lblDisclaimer"; */ +"lblDisclaimer.title" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音官方词库已清除任何「会在法理上妨碍威注音在全球传播」的「与地缘政治及政治意识形态有关的」內容。 ​​​"; /* Class = "NSTextFieldCell"; title = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; ObjectID = "lblCopyright"; */ // "lblCopyright.title" = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index e9be8753f..7cf712e3b 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -15,7 +15,7 @@ "New Version Available" = "有新版可下載"; "Not Now" = "以後再說"; "Visit Website" = "前往網站"; -"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音版本是 %1$@ (%2$@),網路上有更新版本 %3$@ (%4$@) 可供下載。是否要前往威注音網站下載新版來安裝?%5$@"; +"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),網路上有更新版本 %3$@ (%4$@) 可供下載。是否要前往威注音網站下載新版來安裝?%5$@"; "Chinese Conversion" = "優先使用康熙繁體字"; "NotificationSwitchON" = " 已啟用"; "NotificationSwitchOFF" = " 已停用"; diff --git a/Source/zh-Hant.lproj/frmAboutWindow.strings b/Source/zh-Hant.lproj/frmAboutWindow.strings index b0ce427dd..0f3249a10 100644 --- a/Source/zh-Hant.lproj/frmAboutWindow.strings +++ b/Source/zh-Hant.lproj/frmAboutWindow.strings @@ -17,8 +17,8 @@ /* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "lblAppTitle"; */ "lblAppTitle.title" = "vChewing for macOS"; -/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "lblDisclaimer"; */ -"lblDisclaimer.title" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音詞庫已依照《中華人民共和國國家安全法》與《中華人民共和國反分裂國家法》做過合規處理。"; +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; ObjectID = "lblDisclaimer"; */ +"lblDisclaimer.title" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音官方詞庫已清除任何「會在法理上妨礙威注音在全球傳播」的「與地緣政治及政治意識形態有關的」內容。"; /* Class = "NSTextFieldCell"; title = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; ObjectID = "lblCopyright"; */ // "lblCopyright.title" = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; -- Gitee From 9d724ca92740c3cbf4f13cd20e925de83b634510 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 11 Jan 2022 14:26:40 +0800 Subject: [PATCH 027/163] Data Update - 20220111 -- Gitee From 8bc8316f592941ef34ebf85b6517572bc102b8bb Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 10 Jan 2022 20:59:39 +0800 Subject: [PATCH 028/163] Remove unnecessary UpdateNotificationController. --- Source/UpdateNotificationController.h | 54 ---------------- Source/UpdateNotificationController.m | 90 --------------------------- 2 files changed, 144 deletions(-) delete mode 100644 Source/UpdateNotificationController.h delete mode 100644 Source/UpdateNotificationController.m diff --git a/Source/UpdateNotificationController.h b/Source/UpdateNotificationController.h deleted file mode 100644 index 1ad29610b..000000000 --- a/Source/UpdateNotificationController.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// UpdateNotificationController.h -// -// Copyright (c) 2021 The vChewing Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import - -@interface UpdateNotificationController : NSWindowController -{ - NSButton *_visitButton; - NSButton *_laterButton; - NSTextField *_infoTextField; - - NSURL *_siteURL; - NSString *_infoText; -} -- (IBAction)laterAction:(id)sender; -- (IBAction)visitAction:(id)sender; - -@property (assign, nonatomic) IBOutlet NSButton *visitButton; -@property (assign, nonatomic) IBOutlet NSButton *laterButton; -@property (assign, nonatomic) IBOutlet NSTextField *infoTextField; -@property (retain, nonatomic) NSURL *siteURL; -@property (retain, nonatomic) NSString *infoText; -@end diff --git a/Source/UpdateNotificationController.m b/Source/UpdateNotificationController.m deleted file mode 100644 index e69ee3179..000000000 --- a/Source/UpdateNotificationController.m +++ /dev/null @@ -1,90 +0,0 @@ -// -// UpdateNotificationController.m -// -// Copyright (c) 2021 The vChewing Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "UpdateNotificationController.h" - -@implementation UpdateNotificationController -@synthesize visitButton = _visitButton; -@synthesize laterButton = _laterButton; -@synthesize infoTextField = _infoTextField; -@synthesize siteURL = _siteURL; -@synthesize infoText = _infoText; - -- (id)initWithWindow:(NSWindow *)window -{ - self = [super initWithWindow:window]; - if (self) { - _infoText = @"Version info here"; - } - - return self; -} - -- (void)dealloc -{ - [_siteURL release]; - [_infoText release]; - [super dealloc]; -} - -- (void)awakeFromNib -{ - [[self window] setTitle:NSLocalizedString(@"New Version Available", @"")]; - [_visitButton setTitle:NSLocalizedString(@"Visit Website", @"")]; - [_laterButton setTitle:NSLocalizedString(@"Check Later", @"")]; - [_infoTextField setStringValue:_infoText]; - - [[self window] makeFirstResponder:_visitButton]; -} - -- (void)windowDidLoad -{ - [super windowDidLoad]; - - // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. -} - -- (IBAction)laterAction:(id)sender -{ - [[self window] performClose:self]; -} - -- (IBAction)visitAction:(id)sender -{ - if (_siteURL) { - [[NSWorkspace sharedWorkspace] openURL:_siteURL]; - } - [[self window] performClose:self]; -} -@end -- Gitee From 1bb6cb9d826ddf34d0d73a8f7bda793941d965e7 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 10 Jan 2022 21:01:45 +0800 Subject: [PATCH 029/163] Zonble: UserPhrases // Need further modifications for CHS mode. - Raw sync from McBopomofo repository with insufficient compatibility modifications. --- Source/AppDelegate.m | 2 + .../Engine/Gramambular/BlockReadingBuilder.h | 70 +++- Source/InputMethodController.h | 2 + Source/InputMethodController.mm | 311 ++++++++++++++++-- Source/en.lproj/Localizable.strings | 5 + Source/zh-Hans.lproj/Localizable.strings | 5 + Source/zh-Hant.lproj/Localizable.strings | 5 + 7 files changed, 359 insertions(+), 41 deletions(-) diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index 3c66fb3b9..608ef5dc2 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -42,6 +42,7 @@ #import "frmAboutWindow.h" extern void LTLoadLanguageModel(void); +extern void LTLoadUserLanguageModelFile(void); static NSString *kCheckUpdateAutomatically = @"CheckUpdateAutomatically"; static NSString *kNextUpdateCheckDateKey = @"NextUpdateCheckDate"; @@ -66,6 +67,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0; - (void)applicationDidFinishLaunching:(NSNotification *)inNotification { LTLoadLanguageModel(); + LTLoadUserLanguageModelFile(); if (![[NSUserDefaults standardUserDefaults] objectForKey:kCheckUpdateAutomatically]) { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kCheckUpdateAutomatically]; diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index ed6fd1733..43fa3ba50 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -38,7 +38,7 @@ namespace Formosa { class BlockReadingBuilder { public: - BlockReadingBuilder(LanguageModel *inLM); + BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM); void clear(); size_t length() const; @@ -52,7 +52,11 @@ namespace Formosa { void setJoinSeparator(const string& separator); const string joinSeparator() const; - + + size_t markerCursorIndex() const; + void setMarkerCursorIndex(size_t inNewIndex); + vector readingsAtRange(size_t begin, size_t end) const; + Grid& grid(); protected: @@ -60,26 +64,31 @@ namespace Formosa { static const string Join(vector::const_iterator begin, vector::const_iterator end, const string& separator); - //最多使用六個字組成一個詞 + //最多使用六個字組成一個詞 static const size_t MaximumBuildSpanLength = 6; size_t m_cursorIndex; + size_t m_markerCursorIndex; vector m_readings; Grid m_grid; LanguageModel *m_LM; + LanguageModel *m_UserPhraseLM; string m_joinSeparator; }; - inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM) + inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM) : m_LM(inLM) + , m_UserPhraseLM(inUserPhraseLM) , m_cursorIndex(0) + , m_markerCursorIndex(SIZE_MAX) { } inline void BlockReadingBuilder::clear() { m_cursorIndex = 0; + m_markerCursorIndex = SIZE_MAX; m_readings.clear(); m_grid.clear(); } @@ -99,14 +108,36 @@ namespace Formosa { m_cursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex; } + inline size_t BlockReadingBuilder::markerCursorIndex() const + { + return m_markerCursorIndex; + } + + inline void BlockReadingBuilder::setMarkerCursorIndex(size_t inNewIndex) + { + if (inNewIndex == SIZE_MAX) { + m_markerCursorIndex = SIZE_MAX; + return; + } + + m_markerCursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex; + } inline void BlockReadingBuilder::insertReadingAtCursor(const string& inReading) { m_readings.insert(m_readings.begin() + m_cursorIndex, inReading); - m_grid.expandGridByOneAtLocation(m_cursorIndex); + m_grid.expandGridByOneAtLocation(m_cursorIndex); build(); - m_cursorIndex++; + m_cursorIndex++; + } + + inline vector BlockReadingBuilder::readingsAtRange(size_t begin, size_t end) const { + vector v; + for (size_t i = begin; i < end; i++) { + v.push_back(m_readings[i]); + } + return v; } inline bool BlockReadingBuilder::deleteReadingBeforeCursor() @@ -149,7 +180,7 @@ namespace Formosa { build(); } - return true; + return true; } inline void BlockReadingBuilder::setJoinSeparator(const string& separator) @@ -190,10 +221,25 @@ namespace Formosa { for (size_t p = begin ; p < end ; p++) { for (size_t q = 1 ; q <= MaximumBuildSpanLength && p+q <= end ; q++) { string combinedReading = Join(m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator); - - if (m_LM->hasUnigramsForKey(combinedReading) && !m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading)) { - Node n(combinedReading, m_LM->unigramsForKeys(combinedReading), vector()); - m_grid.insertNode(n, p, q); + if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading)) { + vector unigrams; + + if (m_UserPhraseLM != NULL) { + if (m_UserPhraseLM->hasUnigramsForKey(combinedReading)) { + vector userUnigrams = m_UserPhraseLM->unigramsForKeys(combinedReading); + unigrams.insert(unigrams.end(), userUnigrams.begin(), userUnigrams.end()); + } + } + + if (m_LM->hasUnigramsForKey(combinedReading)) { + vector globalUnigrams = m_LM->unigramsForKeys(combinedReading); + unigrams.insert(unigrams.end(), globalUnigrams.begin(), globalUnigrams.end()); + } + + if (unigrams.size() > 0) { + Node n(combinedReading, unigrams, vector()); + m_grid.insertNode(n, p, q); + } } } } @@ -210,7 +256,7 @@ namespace Formosa { } } return result; - } + } } } diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index e8d5b103f..c3f512d72 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -52,6 +52,7 @@ // language model Formosa::Gramambular::FastLM *_languageModel; + Formosa::Gramambular::FastLM *_userPhrasesModel; // the grid (lattice) builder for the unigrams (and bigrams) Formosa::Gramambular::BlockReadingBuilder* _builder; @@ -88,3 +89,4 @@ // the shared language model object extern "C" void LTLoadLanguageModel(); +extern "C" void LTLoadUserLanguageModelFile(); diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index a239b0d4b..76b71fbbc 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -45,6 +45,7 @@ #import "AppDelegate.h" #import "VTHorizontalCandidateController.h" #import "VTVerticalCandidateController.h" +#import "OVNonModalAlertWindowController.h" #import "vChewing-Swift.h" @@ -119,12 +120,69 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // shared language model object that stores our phrase-term probability database FastLM gLanguageModelCHT; FastLM gLanguageModelCHS; +FastLM gUserPhraseLanguageModelCHT; +FastLM gUserPhraseLanguageModelCHS; static const int kUserOverrideModelCapacity = 500; static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife); vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife); +static NSString *LTUserDataFolderPath() +{ + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES); + NSString *appSupportPath = [paths objectAtIndex:0]; + NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"]; + return userDictPath; +} + +static NSString *LTUserPhrasesDataPathCHT() +{ + return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-cht.txt"]; +} + +// static NSString *LTUserPhrasesDataPathCHS() +// { + // return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-chs.txt"]; + // } + +static BOOL LTCheckIfUserLanguageModelFileExists() { + + NSString *folderPath = LTUserDataFolderPath(); + BOOL isFolder = NO; + BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; + if (folderExist && !isFolder) { + NSError *error = nil; + [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; + if (error) { + NSLog(@"Failed to remove folder %@", error); + return NO; + } + folderExist = NO; + } + if (!folderExist) { + NSError *error = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error]; + if (error) { + NSLog(@"Failed to create folder %@", error); + return NO; + } + } + + NSString *filePath = LTUserPhrasesDataPathCHT(); + if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES]; + if (!result) { + NSLog(@"Failed to write file"); + return NO; + } + } + return YES; +} + + + + // https://clang-analyzer.llvm.org/faq.html __attribute__((annotate("returns_localized_nsstring"))) static inline NSString *LocalizationNotNeeded(NSString *s) { @@ -200,7 +258,8 @@ static double FindHighestScore(const vector& nodes, double epsilon) // create the lattice builder _languageModel = &gLanguageModelCHT; - _builder = new BlockReadingBuilder(_languageModel); + _userPhrasesModel = &gUserPhraseLanguageModelCHT; + _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); _uom = &gUserOverrideModelCHT; // each Mandarin syllable is separated by a hyphen @@ -232,11 +291,28 @@ static double FindHighestScore(const vector& nodes, double epsilon) chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; [menu addItem:chineseConversionMenuItem]; + // Needs improvement for Simplified Chinese Input + if (_inputMode != kSimpBopomofoModeIdentifier) { + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""]; + NSMenuItem *editUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; + [editUserPheaseItem setIndentationLevel:2]; + [menu addItem:editUserPheaseItem]; + + NSMenuItem *reloadUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; + [reloadUserPheaseItem setIndentationLevel:2]; + [menu addItem:reloadUserPheaseItem]; + [menu addItem:[NSMenuItem separatorItem]]; + } + NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; [menu addItem:updateCheckItem]; NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""]; [menu addItem:aboutMenuItem]; + + // Menu Debug Purposes... + NSLog(@"menu %@", menu); return menu; } @@ -333,24 +409,24 @@ static double FindHighestScore(const vector& nodes, double epsilon) { NSString *newInputMode; Formosa::Gramambular::FastLM *newLanguageModel; - vChewing::UserOverrideModel *newUom; + Formosa::Gramambular::FastLM *userPhraseModel; if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) { newInputMode = kSimpBopomofoModeIdentifier; newLanguageModel = &gLanguageModelCHS; - newUom = &gUserOverrideModelCHS; + userPhraseModel = &gUserPhraseLanguageModelCHS; } else { newInputMode = kBopomofoModeIdentifier; newLanguageModel = &gLanguageModelCHT; - newUom = &gUserOverrideModelCHT; + userPhraseModel = &gUserPhraseLanguageModelCHT; } // Only apply the changes if the value is changed if (![_inputMode isEqualToString:newInputMode]) { [[NSUserDefaults standardUserDefaults] synchronize]; - // Remember to override the keyboard layout again -- treat this as an activate eventy + // Remember to override the keyboard layout again -- treat this as an activate event NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; if (!basisKeyboardLayoutID) { basisKeyboardLayoutID = @"com.apple.keylayout.US"; @@ -359,6 +435,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) _inputMode = newInputMode; _languageModel = newLanguageModel; + _userPhrasesModel = userPhraseModel; if (!_bpmfReadingBuffer->isEmpty()) { _bpmfReadingBuffer->clear(); @@ -371,10 +448,9 @@ static double FindHighestScore(const vector& nodes, double epsilon) if (_builder) { delete _builder; - _builder = new BlockReadingBuilder(_languageModel); + _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); _builder->setJoinSeparator("-"); } - _uom = newUom; } } @@ -408,6 +484,9 @@ static double FindHighestScore(const vector& nodes, double epsilon) [_candidates removeAllObjects]; } +NS_INLINE size_t min(size_t a, size_t b) { return a < b ? a : b; } +NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } + // TODO: bug #28 is more likely to live in this method. - (void)updateClientComposingBuffer:(id)client { @@ -461,17 +540,39 @@ static double FindHighestScore(const vector& nodes, double epsilon) NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; NSInteger cursorIndex = composedStringCursorIndex + [reading length]; - // we must use NSAttributedString so that the cursor is visible -- - // can't just use NSString - NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), - NSMarkedClauseSegmentAttributeName: @0}; - NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText attributes:attrDict]; - - // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, - // i.e. the client app needs to take care of where to put ths composing buffer - [client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; + if (_bpmfReadingBuffer->isEmpty() && _builder->markerCursorIndex() != SIZE_MAX) { + // if there is a marked range, we need to tear the string into three parts. + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText]; + size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); + size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); + [attrString setAttributes:@{ + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), + NSMarkedClauseSegmentAttributeName: @0 + } range:NSMakeRange(0, begin)]; + [attrString setAttributes:@{ + NSUnderlineStyleAttributeName: @(NSUnderlineStyleThick), + NSMarkedClauseSegmentAttributeName: @1 + } range:NSMakeRange(begin, end - begin)]; + [attrString setAttributes:@{ + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), + NSMarkedClauseSegmentAttributeName: @2 + } range:NSMakeRange(end, [composedText length] - end)]; + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, + // i.e. the client app needs to take care of where to put ths composing buffer + [client setMarkedText:attrString selectionRange:NSMakeRange((NSInteger)_builder->markerCursorIndex(), 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; + _latestReadingCursor = (NSInteger)_builder->markerCursorIndex(); + } else { + // we must use NSAttributedString so that the cursor is visible -- + // can't just use NSString + NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), + NSMarkedClauseSegmentAttributeName: @0}; + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText attributes:attrDict]; - _latestReadingCursor = cursorIndex; + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, + // i.e. the client app needs to take care of where to put ths composing buffer + [client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; + _latestReadingCursor = cursorIndex; + } } - (void)walk @@ -575,6 +676,64 @@ static double FindHighestScore(const vector& nodes, double epsilon) return layout; } +- (NSString *)_currentMarkedText +{ + if (_builder->markerCursorIndex() < 0) { + return @""; + } + if (!_bpmfReadingBuffer->isEmpty()) { + return @""; + } + + size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); + size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); + // A phrase should contian at least two characters. + if (end - begin < 2) { + return @""; + } + + NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); + NSString *reading = [_composingBuffer substringWithRange:range]; + NSMutableString *string = [[NSMutableString alloc] init]; + [string appendString:reading]; + [string appendString:@" "]; + NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; + vector v = _builder->readingsAtRange(begin,end); + for(vector::iterator it_i=v.begin(); it_i!=v.end(); ++it_i) { + [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; + } + [string appendString:[readingsArray componentsJoinedByString:@"-"]]; + [string appendString:@" "]; + [string appendString:@"-1.0"]; + return string; +} + +- (BOOL)_writeUserPhrase +{ + if (!LTCheckIfUserLanguageModelFileExists()) { + return NO; + } + + NSString *currentMarkedPhrase = [self _currentMarkedText]; + if (![currentMarkedPhrase length]) { + return NO; + } + + currentMarkedPhrase = [currentMarkedPhrase stringByAppendingString:@"\n"]; + NSString *path = LTUserPhrasesDataPathCHT(); + NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; + if (!file) { + return NO; + } + [file seekToEndOfFile]; + NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; + [file writeData:data]; + [file closeFile]; + + LTLoadUserLanguageModelFile(); + return YES; +} + - (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client { NSRect textFrame = NSZeroRect; @@ -615,7 +774,6 @@ static double FindHighestScore(const vector& nodes, double epsilon) return NO; } - // Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo. if (charCode == 8 || charCode == 13 || keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey || keyCode == cursorForwardKey || keyCode == cursorBackwardKey) { // do nothing if backspace is pressed -- we ignore the key @@ -659,6 +817,50 @@ static double FindHighestScore(const vector& nodes, double epsilon) return [self handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode]; } + // If we have marker index. + if (_builder->markerCursorIndex() != SIZE_MAX) { + // ESC + if (charCode == 27) { + _builder->setMarkerCursorIndex(SIZE_MAX); + [self updateClientComposingBuffer:client]; + return YES; + } + // Enter + if (charCode == 13) { + if ([self _writeUserPhrase]) { + _builder->setMarkerCursorIndex(SIZE_MAX); + } else { + [self beep]; + } + [self updateClientComposingBuffer:client]; + return YES; + } + // Shift + left + if (keyCode == cursorBackwardKey && (flags & NSShiftKeyMask)) { + if (_builder->markerCursorIndex() > 0) { + _builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1); + } + else { + [self beep]; + } + [self updateClientComposingBuffer:client]; + return YES; + } + // Shift + Right + if (keyCode == cursorForwardKey && (flags & NSShiftKeyMask)) { + if (_builder->markerCursorIndex() < _builder->length()) { + _builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1); + } + else { + [self beep]; + } + [self updateClientComposingBuffer:client]; + return YES; + } + + _builder->setMarkerCursorIndex(SIZE_MAX); + } + // see if it's valid BPMF reading if (_bpmfReadingBuffer->isValidKey((char)charCode)) { _bpmfReadingBuffer->combineKey((char)charCode); @@ -781,11 +983,21 @@ static double FindHighestScore(const vector& nodes, double epsilon) return NO; } - if (_builder->cursorIndex() > 0) { - _builder->setCursorIndex(_builder->cursorIndex() - 1); - } - else { - [self beep]; + if (flags & NSShiftKeyMask) { + // Shift + left + if (_builder->cursorIndex() > 0) { + _builder->setMarkerCursorIndex(_builder->cursorIndex() - 1); + } + else { + [self beep]; + } + } else { + if (_builder->cursorIndex() > 0) { + _builder->setCursorIndex(_builder->cursorIndex() - 1); + } + else { + [self beep]; + } } } @@ -803,11 +1015,20 @@ static double FindHighestScore(const vector& nodes, double epsilon) return NO; } - if (_builder->cursorIndex() < _builder->length()) { - _builder->setCursorIndex(_builder->cursorIndex() + 1); - } - else { - [self beep]; + if (flags & NSShiftKeyMask) { + // Shift + Right + if (_builder->cursorIndex() < _builder->length()) { + _builder->setMarkerCursorIndex(_builder->cursorIndex() + 1); + } else { + [self beep]; + } + } else { + if (_builder->cursorIndex() < _builder->length()) { + _builder->setCursorIndex(_builder->cursorIndex() + 1); + } + else { + [self beep]; + } } } @@ -1333,6 +1554,30 @@ static double FindHighestScore(const vector& nodes, double epsilon) [(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES]; } +- (void)openUserPhrases:(id)sender +{ + NSLog(@"openUserPhrases called"); + if (!LTCheckIfUserLanguageModelFileExists()) { + NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()]; + [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; + return; + } + + NSString *path = LTUserPhrasesDataPathCHT(); + NSLog(@"Open %@", path); + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES]; + } + NSURL *url = [NSURL fileURLWithPath:path]; + [[NSWorkspace sharedWorkspace] openURL:url]; +} + +- (void)reloadUserPhrases:(id)sender +{ + NSLog(@"reloadUserPhrases called"); + LTLoadUserLanguageModelFile(); +} + - (void)showAbout:(id)sender { // show the About window, and also make the IME app itself the focus @@ -1404,9 +1649,17 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM & } } - void LTLoadLanguageModel() { LTLoadLanguageModelFile(@"data", gLanguageModelCHT); LTLoadLanguageModelFile(@"data-chs", gLanguageModelCHS); } + +void LTLoadUserLanguageModelFile() +{ + gUserPhraseLanguageModelCHT.close(); + bool result = gUserPhraseLanguageModelCHT.open([LTUserPhrasesDataPathCHT() UTF8String]); + if (!result) { + NSLog(@"Failed opening language model for CHT user phrases."); + } +} diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index c7ae84cae..b3d7268db 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -19,3 +19,8 @@ "Chinese Conversion" = "Convert zh-TW Kanji to KangXi Variants"; "NotificationSwitchON" = " ON"; "NotificationSwitchOFF" = " OFF"; +"User Phrases" = "User Phrases"; +"Edit User Phrases" = "Edit User Phrases"; +"Reload User Phrases" = "Reload User Phrases"; +"Unable to create the user phrase file." = "Unable to create the user phrase file."; +"Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\"."; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 52421b076..c2c7311cc 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -19,3 +19,8 @@ "Chinese Conversion" = "优先使用康熙繁体字"; "NotificationSwitchON" = " 已启用"; "NotificationSwitchOFF" = " 已停用"; +"User Phrases" = "自订语汇"; +"Edit User Phrases" = "编辑自订语汇"; +"Reload User Phrases" = "重载自订语汇"; +"Unable to create the user phrase file." = "无法创建自订语汇档案。"; +"Please check the permission of at \"%@\"." = "请检查此处的存取权限:\"%@\"."; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 7cf712e3b..a3b24b4f1 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -19,3 +19,8 @@ "Chinese Conversion" = "優先使用康熙繁體字"; "NotificationSwitchON" = " 已啟用"; "NotificationSwitchOFF" = " 已停用"; +"User Phrases" = "自訂語彙"; +"Edit User Phrases" = "編輯自訂語彙"; +"Reload User Phrases" = "重載自訂語彙"; +"Unable to create the user phrase file." = "無法創建自訂語彙檔案。"; +"Please check the permission of at \"%@\"." = "請檢查此處的存取權限:\"%@\"."; -- Gitee From ea356d6d6722c6a3715ad95f84dddfb2f10b0f1d Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 11 Jan 2022 22:59:39 +0800 Subject: [PATCH 030/163] Shiki #13: UserPhrases // CHS mode compatibility. - Also remove useless menu entry. --- Source/AppDelegate.m | 8 +- Source/InputMethodController.h | 3 +- Source/InputMethodController.mm | 327 ++++++++++++++--------- Source/en.lproj/Localizable.strings | 1 - Source/zh-Hans.lproj/Localizable.strings | 1 - Source/zh-Hant.lproj/Localizable.strings | 1 - 6 files changed, 215 insertions(+), 126 deletions(-) diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index 608ef5dc2..5d9e2fa37 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -42,7 +42,8 @@ #import "frmAboutWindow.h" extern void LTLoadLanguageModel(void); -extern void LTLoadUserLanguageModelFile(void); +extern void LTLoadUserLanguageModelFileCHT(void); +extern void LTLoadUserLanguageModelFileCHS(void); static NSString *kCheckUpdateAutomatically = @"CheckUpdateAutomatically"; static NSString *kNextUpdateCheckDateKey = @"NextUpdateCheckDate"; @@ -67,9 +68,10 @@ static const NSTimeInterval kTimeoutInterval = 60.0; - (void)applicationDidFinishLaunching:(NSNotification *)inNotification { LTLoadLanguageModel(); - LTLoadUserLanguageModelFile(); + LTLoadUserLanguageModelFileCHT(); + LTLoadUserLanguageModelFileCHS(); - if (![[NSUserDefaults standardUserDefaults] objectForKey:kCheckUpdateAutomatically]) { + if (![[NSUserDefaults standardUserDefaults] objectForKey:kCheckUpdateAutomatically]) { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kCheckUpdateAutomatically]; [[NSUserDefaults standardUserDefaults] synchronize]; } diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index c3f512d72..b02edb273 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -89,4 +89,5 @@ // the shared language model object extern "C" void LTLoadLanguageModel(); -extern "C" void LTLoadUserLanguageModelFile(); +extern "C" void LTLoadUserLanguageModelFileCHT(); +extern "C" void LTLoadUserLanguageModelFileCHS(); diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 76b71fbbc..6796bfdbc 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -141,48 +141,52 @@ static NSString *LTUserPhrasesDataPathCHT() return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-cht.txt"]; } -// static NSString *LTUserPhrasesDataPathCHS() -// { - // return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-chs.txt"]; - // } +static NSString *LTUserPhrasesDataPathCHS() +{ + return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-chs.txt"]; +} static BOOL LTCheckIfUserLanguageModelFileExists() { - NSString *folderPath = LTUserDataFolderPath(); - BOOL isFolder = NO; - BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; - if (folderExist && !isFolder) { - NSError *error = nil; - [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; - if (error) { - NSLog(@"Failed to remove folder %@", error); - return NO; - } - folderExist = NO; - } - if (!folderExist) { - NSError *error = nil; - [[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error]; - if (error) { - NSLog(@"Failed to create folder %@", error); - return NO; - } - } - - NSString *filePath = LTUserPhrasesDataPathCHT(); - if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { - BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES]; - if (!result) { - NSLog(@"Failed to write file"); - return NO; - } - } - return YES; + NSString *folderPath = LTUserDataFolderPath(); + BOOL isFolder = NO; + BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; + if (folderExist && !isFolder) { + NSError *error = nil; + [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; + if (error) { + NSLog(@"Failed to remove folder %@", error); + return NO; + } + folderExist = NO; + } + if (!folderExist) { + NSError *error = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error]; + if (error) { + NSLog(@"Failed to create folder %@", error); + return NO; + } + } + NSString *filePathCHS = LTUserPhrasesDataPathCHS(); + if (![[NSFileManager defaultManager] fileExistsAtPath:filePathCHS]) { + BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePathCHS atomically:YES]; + if (!result) { + NSLog(@"Failed to write userdict CHS file"); + return NO; + } + } + NSString *filePathCHT = LTUserPhrasesDataPathCHT(); + if (![[NSFileManager defaultManager] fileExistsAtPath:filePathCHT]) { + BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePathCHT atomically:YES]; + if (!result) { + NSLog(@"Failed to write userdict CHT file"); + return NO; + } + } + return YES; } - - - // https://clang-analyzer.llvm.org/faq.html __attribute__((annotate("returns_localized_nsstring"))) static inline NSString *LocalizationNotNeeded(NSString *s) { @@ -260,7 +264,13 @@ static double FindHighestScore(const vector& nodes, double epsilon) _languageModel = &gLanguageModelCHT; _userPhrasesModel = &gUserPhraseLanguageModelCHT; _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); - _uom = &gUserOverrideModelCHT; + if (_inputMode == kSimpBopomofoModeIdentifier) { + NSLog(@"gUserOverrideModelCHS called"); + _uom = &gUserOverrideModelCHS; + } else { + NSLog(@"gUserOverrideModelCHT called"); + _uom = &gUserOverrideModelCHT; + } // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -291,28 +301,26 @@ static double FindHighestScore(const vector& nodes, double epsilon) chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; [menu addItem:chineseConversionMenuItem]; - // Needs improvement for Simplified Chinese Input - if (_inputMode != kSimpBopomofoModeIdentifier) { - [menu addItem:[NSMenuItem separatorItem]]; - [menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""]; - NSMenuItem *editUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; - [editUserPheaseItem setIndentationLevel:2]; - [menu addItem:editUserPheaseItem]; - - NSMenuItem *reloadUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; - [reloadUserPheaseItem setIndentationLevel:2]; - [menu addItem:reloadUserPheaseItem]; - [menu addItem:[NSMenuItem separatorItem]]; - } + [menu addItem:[NSMenuItem separatorItem]]; // ----------------------- + + NSMenuItem *editUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; + [editUserPheaseItem setIndentationLevel:2]; + [menu addItem:editUserPheaseItem]; + + NSMenuItem *reloadUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; + [reloadUserPheaseItem setIndentationLevel:2]; + [menu addItem:reloadUserPheaseItem]; + + [menu addItem:[NSMenuItem separatorItem]]; // ----------------------- NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; [menu addItem:updateCheckItem]; NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""]; [menu addItem:aboutMenuItem]; - - // Menu Debug Purposes... - NSLog(@"menu %@", menu); + + // Menu Debug Purposes... + NSLog(@"menu %@", menu); return menu; } @@ -410,46 +418,85 @@ static double FindHighestScore(const vector& nodes, double epsilon) NSString *newInputMode; Formosa::Gramambular::FastLM *newLanguageModel; Formosa::Gramambular::FastLM *userPhraseModel; + vChewing::UserOverrideModel *newUom; + + if ([value isKindOfClass:[NSString class]]) { + + if ([value isEqual:kSimpBopomofoModeIdentifier]) { + + newInputMode = kSimpBopomofoModeIdentifier; + newLanguageModel = &gLanguageModelCHS; + newUom = &gUserOverrideModelCHS; + userPhraseModel = &gUserPhraseLanguageModelCHS; + + if (![_inputMode isEqualToString:newInputMode]) { + [[NSUserDefaults standardUserDefaults] synchronize]; + + // Remember to override the keyboard layout again -- treat this as an activate event + NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; + if (!basisKeyboardLayoutID) { + basisKeyboardLayoutID = @"com.apple.keylayout.US"; + } + [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) { - newInputMode = kSimpBopomofoModeIdentifier; - newLanguageModel = &gLanguageModelCHS; - userPhraseModel = &gUserPhraseLanguageModelCHS; - } - else { - newInputMode = kBopomofoModeIdentifier; - newLanguageModel = &gLanguageModelCHT; - userPhraseModel = &gUserPhraseLanguageModelCHT; - } + _inputMode = newInputMode; + _languageModel = newLanguageModel; + _userPhrasesModel = userPhraseModel; - // Only apply the changes if the value is changed - if (![_inputMode isEqualToString:newInputMode]) { - [[NSUserDefaults standardUserDefaults] synchronize]; + if (!_bpmfReadingBuffer->isEmpty()) { + _bpmfReadingBuffer->clear(); + [self updateClientComposingBuffer:sender]; + } - // Remember to override the keyboard layout again -- treat this as an activate event - NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; - if (!basisKeyboardLayoutID) { - basisKeyboardLayoutID = @"com.apple.keylayout.US"; - } - [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; + if ([_composingBuffer length] > 0) { + [self commitComposition:sender]; + } - _inputMode = newInputMode; - _languageModel = newLanguageModel; - _userPhrasesModel = userPhraseModel; + if (_builder) { + delete _builder; + _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); + _builder->setJoinSeparator("-"); + } + } + _uom = newUom; + + } else if ([value isEqual:kBopomofoModeIdentifier]) { + + newInputMode = kBopomofoModeIdentifier; + newLanguageModel = &gLanguageModelCHT; + userPhraseModel = &gUserPhraseLanguageModelCHT; + newUom = &gUserOverrideModelCHT; + + if (![_inputMode isEqualToString:newInputMode]) { + [[NSUserDefaults standardUserDefaults] synchronize]; + + // Remember to override the keyboard layout again -- treat this as an activate event + NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; + if (!basisKeyboardLayoutID) { + basisKeyboardLayoutID = @"com.apple.keylayout.US"; + } + [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - if (!_bpmfReadingBuffer->isEmpty()) { - _bpmfReadingBuffer->clear(); - [self updateClientComposingBuffer:sender]; - } + _inputMode = newInputMode; + _languageModel = newLanguageModel; + _userPhrasesModel = userPhraseModel; - if ([_composingBuffer length] > 0) { - [self commitComposition:sender]; - } + if (!_bpmfReadingBuffer->isEmpty()) { + _bpmfReadingBuffer->clear(); + [self updateClientComposingBuffer:sender]; + } + + if ([_composingBuffer length] > 0) { + [self commitComposition:sender]; + } - if (_builder) { - delete _builder; - _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); - _builder->setJoinSeparator("-"); + if (_builder) { + delete _builder; + _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); + _builder->setJoinSeparator("-"); + } + } + _uom = newUom; } } } @@ -710,28 +757,42 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (BOOL)_writeUserPhrase { - if (!LTCheckIfUserLanguageModelFileExists()) { - return NO; - } + if (!LTCheckIfUserLanguageModelFileExists()) { + return NO; + } - NSString *currentMarkedPhrase = [self _currentMarkedText]; + NSString *currentMarkedPhrase = [self _currentMarkedText]; if (![currentMarkedPhrase length]) { return NO; } currentMarkedPhrase = [currentMarkedPhrase stringByAppendingString:@"\n"]; - NSString *path = LTUserPhrasesDataPathCHT(); - NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; - if (!file) { - return NO; + + if (_inputMode == kSimpBopomofoModeIdentifier) { + NSString *path = LTUserPhrasesDataPathCHS(); + NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; + if (!file) { + return NO; + } + [file seekToEndOfFile]; + NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; + [file writeData:data]; + [file closeFile]; + LTLoadUserLanguageModelFileCHS(); + return YES; + } else { + NSString *path = LTUserPhrasesDataPathCHT(); + NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; + if (!file) { + return NO; + } + [file seekToEndOfFile]; + NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; + [file writeData:data]; + [file closeFile]; + LTLoadUserLanguageModelFileCHT(); + return YES; } - [file seekToEndOfFile]; - NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; - [file writeData:data]; - [file closeFile]; - - LTLoadUserLanguageModelFile(); - return YES; } - (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client @@ -1556,26 +1617,45 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)openUserPhrases:(id)sender { - NSLog(@"openUserPhrases called"); - if (!LTCheckIfUserLanguageModelFileExists()) { - NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()]; - [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; - return; - } - - NSString *path = LTUserPhrasesDataPathCHT(); - NSLog(@"Open %@", path); - if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { - [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES]; - } - NSURL *url = [NSURL fileURLWithPath:path]; - [[NSWorkspace sharedWorkspace] openURL:url]; + NSLog(@"openUserPhrases called"); + if (!LTCheckIfUserLanguageModelFileExists()) { + NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()]; + [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; + return; + } + + if (_inputMode == kSimpBopomofoModeIdentifier) { + NSLog(@"editUserPhrases CHS called"); + NSString *path = LTUserPhrasesDataPathCHS(); + NSLog(@"Open %@", path); + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES]; + } + NSURL *url = [NSURL fileURLWithPath:path]; + [[NSWorkspace sharedWorkspace] openURL:url]; + } else { + NSLog(@"editUserPhrases CHT called"); + NSString *path = LTUserPhrasesDataPathCHT(); + NSLog(@"Open %@", path); + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES]; + } + NSURL *url = [NSURL fileURLWithPath:path]; + [[NSWorkspace sharedWorkspace] openURL:url]; + } + + } - (void)reloadUserPhrases:(id)sender { - NSLog(@"reloadUserPhrases called"); - LTLoadUserLanguageModelFile(); + if (_inputMode == kSimpBopomofoModeIdentifier) { + NSLog(@"reloadUserPhrases CHS called"); + LTLoadUserLanguageModelFileCHS(); + } else { + NSLog(@"reloadUserPhrases CHT called"); + LTLoadUserLanguageModelFileCHT(); + } } - (void)showAbout:(id)sender @@ -1655,7 +1735,7 @@ void LTLoadLanguageModel() LTLoadLanguageModelFile(@"data-chs", gLanguageModelCHS); } -void LTLoadUserLanguageModelFile() +void LTLoadUserLanguageModelFileCHT() { gUserPhraseLanguageModelCHT.close(); bool result = gUserPhraseLanguageModelCHT.open([LTUserPhrasesDataPathCHT() UTF8String]); @@ -1663,3 +1743,12 @@ void LTLoadUserLanguageModelFile() NSLog(@"Failed opening language model for CHT user phrases."); } } + +void LTLoadUserLanguageModelFileCHS() +{ + gUserPhraseLanguageModelCHS.close(); + bool result = gUserPhraseLanguageModelCHS.open([LTUserPhrasesDataPathCHS() UTF8String]); + if (!result) { + NSLog(@"Failed opening language model for CHT user phrases."); + } +} diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index b3d7268db..a82c6261e 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -19,7 +19,6 @@ "Chinese Conversion" = "Convert zh-TW Kanji to KangXi Variants"; "NotificationSwitchON" = " ON"; "NotificationSwitchOFF" = " OFF"; -"User Phrases" = "User Phrases"; "Edit User Phrases" = "Edit User Phrases"; "Reload User Phrases" = "Reload User Phrases"; "Unable to create the user phrase file." = "Unable to create the user phrase file."; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index c2c7311cc..d3f45f5a2 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -19,7 +19,6 @@ "Chinese Conversion" = "优先使用康熙繁体字"; "NotificationSwitchON" = " 已启用"; "NotificationSwitchOFF" = " 已停用"; -"User Phrases" = "自订语汇"; "Edit User Phrases" = "编辑自订语汇"; "Reload User Phrases" = "重载自订语汇"; "Unable to create the user phrase file." = "无法创建自订语汇档案。"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index a3b24b4f1..a36c50b87 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -19,7 +19,6 @@ "Chinese Conversion" = "優先使用康熙繁體字"; "NotificationSwitchON" = " 已啟用"; "NotificationSwitchOFF" = " 已停用"; -"User Phrases" = "自訂語彙"; "Edit User Phrases" = "編輯自訂語彙"; "Reload User Phrases" = "重載自訂語彙"; "Unable to create the user phrase file." = "無法創建自訂語彙檔案。"; -- Gitee From 81cf4b5f2580bd951cc11887a4ce082df8fe42d5 Mon Sep 17 00:00:00 2001 From: Hiraku Date: Tue, 11 Jan 2022 22:59:51 +0800 Subject: [PATCH 031/163] Hiraku #13: UserPhrases // Optimize the CHS Compatibility Patch. --- Source/AppDelegate.m | 6 +- Source/InputMethodController.h | 3 +- Source/InputMethodController.mm | 175 ++++++++++---------------------- 3 files changed, 58 insertions(+), 126 deletions(-) diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index 5d9e2fa37..55eca7046 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -42,8 +42,7 @@ #import "frmAboutWindow.h" extern void LTLoadLanguageModel(void); -extern void LTLoadUserLanguageModelFileCHT(void); -extern void LTLoadUserLanguageModelFileCHS(void); +extern void LTLoadUserLanguageModelFile(void); static NSString *kCheckUpdateAutomatically = @"CheckUpdateAutomatically"; static NSString *kNextUpdateCheckDateKey = @"NextUpdateCheckDate"; @@ -68,8 +67,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0; - (void)applicationDidFinishLaunching:(NSNotification *)inNotification { LTLoadLanguageModel(); - LTLoadUserLanguageModelFileCHT(); - LTLoadUserLanguageModelFileCHS(); + LTLoadUserLanguageModelFile(); if (![[NSUserDefaults standardUserDefaults] objectForKey:kCheckUpdateAutomatically]) { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kCheckUpdateAutomatically]; diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index b02edb273..c3f512d72 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -89,5 +89,4 @@ // the shared language model object extern "C" void LTLoadLanguageModel(); -extern "C" void LTLoadUserLanguageModelFileCHT(); -extern "C" void LTLoadUserLanguageModelFileCHS(); +extern "C" void LTLoadUserLanguageModelFile(); diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 6796bfdbc..abb03e3fa 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -264,13 +264,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) _languageModel = &gLanguageModelCHT; _userPhrasesModel = &gUserPhraseLanguageModelCHT; _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); - if (_inputMode == kSimpBopomofoModeIdentifier) { - NSLog(@"gUserOverrideModelCHS called"); - _uom = &gUserOverrideModelCHS; - } else { - NSLog(@"gUserOverrideModelCHT called"); - _uom = &gUserOverrideModelCHT; - } + _uom = &gUserOverrideModelCHT; // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -420,83 +414,48 @@ static double FindHighestScore(const vector& nodes, double epsilon) Formosa::Gramambular::FastLM *userPhraseModel; vChewing::UserOverrideModel *newUom; - if ([value isKindOfClass:[NSString class]]) { - - if ([value isEqual:kSimpBopomofoModeIdentifier]) { - - newInputMode = kSimpBopomofoModeIdentifier; - newLanguageModel = &gLanguageModelCHS; - newUom = &gUserOverrideModelCHS; - userPhraseModel = &gUserPhraseLanguageModelCHS; - - if (![_inputMode isEqualToString:newInputMode]) { - [[NSUserDefaults standardUserDefaults] synchronize]; - - // Remember to override the keyboard layout again -- treat this as an activate event - NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; - if (!basisKeyboardLayoutID) { - basisKeyboardLayoutID = @"com.apple.keylayout.US"; - } - [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - - _inputMode = newInputMode; - _languageModel = newLanguageModel; - _userPhrasesModel = userPhraseModel; - - if (!_bpmfReadingBuffer->isEmpty()) { - _bpmfReadingBuffer->clear(); - [self updateClientComposingBuffer:sender]; - } + if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) { + newInputMode = kSimpBopomofoModeIdentifier; + newLanguageModel = &gLanguageModelCHS; + newUom = &gUserOverrideModelCHS; + userPhraseModel = &gUserPhraseLanguageModelCHS; + } + else { + newInputMode = kBopomofoModeIdentifier; + newLanguageModel = &gLanguageModelCHT; + newUom = &gUserOverrideModelCHT; + userPhraseModel = &gUserPhraseLanguageModelCHT; + } - if ([_composingBuffer length] > 0) { - [self commitComposition:sender]; - } + // Only apply the changes if the value is changed + if (![_inputMode isEqualToString:newInputMode]) { + [[NSUserDefaults standardUserDefaults] synchronize]; - if (_builder) { - delete _builder; - _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); - _builder->setJoinSeparator("-"); - } - } - _uom = newUom; - - } else if ([value isEqual:kBopomofoModeIdentifier]) { - - newInputMode = kBopomofoModeIdentifier; - newLanguageModel = &gLanguageModelCHT; - userPhraseModel = &gUserPhraseLanguageModelCHT; - newUom = &gUserOverrideModelCHT; - - if (![_inputMode isEqualToString:newInputMode]) { - [[NSUserDefaults standardUserDefaults] synchronize]; - - // Remember to override the keyboard layout again -- treat this as an activate event - NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; - if (!basisKeyboardLayoutID) { - basisKeyboardLayoutID = @"com.apple.keylayout.US"; - } - [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; + // Remember to override the keyboard layout again -- treat this as an activate event + NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; + if (!basisKeyboardLayoutID) { + basisKeyboardLayoutID = @"com.apple.keylayout.US"; + } + [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - _inputMode = newInputMode; - _languageModel = newLanguageModel; - _userPhrasesModel = userPhraseModel; + _inputMode = newInputMode; + _languageModel = newLanguageModel; + _userPhrasesModel = userPhraseModel; + _uom = newUom; - if (!_bpmfReadingBuffer->isEmpty()) { - _bpmfReadingBuffer->clear(); - [self updateClientComposingBuffer:sender]; - } + if (!_bpmfReadingBuffer->isEmpty()) { + _bpmfReadingBuffer->clear(); + [self updateClientComposingBuffer:sender]; + } - if ([_composingBuffer length] > 0) { - [self commitComposition:sender]; - } + if ([_composingBuffer length] > 0) { + [self commitComposition:sender]; + } - if (_builder) { - delete _builder; - _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); - _builder->setJoinSeparator("-"); - } - } - _uom = newUom; + if (_builder) { + delete _builder; + _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); + _builder->setJoinSeparator("-"); } } } @@ -768,31 +727,17 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } currentMarkedPhrase = [currentMarkedPhrase stringByAppendingString:@"\n"]; - if (_inputMode == kSimpBopomofoModeIdentifier) { - NSString *path = LTUserPhrasesDataPathCHS(); - NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; - if (!file) { - return NO; - } - [file seekToEndOfFile]; - NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; - [file writeData:data]; - [file closeFile]; - LTLoadUserLanguageModelFileCHS(); - return YES; - } else { - NSString *path = LTUserPhrasesDataPathCHT(); - NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; - if (!file) { - return NO; - } - [file seekToEndOfFile]; - NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; - [file writeData:data]; - [file closeFile]; - LTLoadUserLanguageModelFileCHT(); - return YES; + NSString *path = _inputMode == kSimpBopomofoModeIdentifier ? LTUserPhrasesDataPathCHS() : LTUserPhrasesDataPathCHT(); + NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; + if (!file) { + return NO; } + [file seekToEndOfFile]; + NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; + [file writeData:data]; + [file closeFile]; + LTLoadUserLanguageModelFile(); + return YES; } - (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client @@ -1649,13 +1594,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)reloadUserPhrases:(id)sender { - if (_inputMode == kSimpBopomofoModeIdentifier) { - NSLog(@"reloadUserPhrases CHS called"); - LTLoadUserLanguageModelFileCHS(); - } else { - NSLog(@"reloadUserPhrases CHT called"); - LTLoadUserLanguageModelFileCHT(); - } + LTLoadUserLanguageModelFile(); } - (void)showAbout:(id)sender @@ -1735,20 +1674,16 @@ void LTLoadLanguageModel() LTLoadLanguageModelFile(@"data-chs", gLanguageModelCHS); } -void LTLoadUserLanguageModelFileCHT() +void LTLoadUserLanguageModelFile() { gUserPhraseLanguageModelCHT.close(); - bool result = gUserPhraseLanguageModelCHT.open([LTUserPhrasesDataPathCHT() UTF8String]); - if (!result) { - NSLog(@"Failed opening language model for CHT user phrases."); - } -} - -void LTLoadUserLanguageModelFileCHS() -{ gUserPhraseLanguageModelCHS.close(); - bool result = gUserPhraseLanguageModelCHS.open([LTUserPhrasesDataPathCHS() UTF8String]); - if (!result) { + bool resultCHT = gUserPhraseLanguageModelCHT.open([LTUserPhrasesDataPathCHT() UTF8String]); + bool resultCHS = gUserPhraseLanguageModelCHS.open([LTUserPhrasesDataPathCHS() UTF8String]); + if (!resultCHT) { NSLog(@"Failed opening language model for CHT user phrases."); } + if (!resultCHS) { + NSLog(@"Failed opening language model for CHS user phrases."); + } } -- Gitee From 4ffd29d9dc876ce0a1a1af644ae0c6c02d3015b4 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 12 Jan 2022 00:59:52 +0800 Subject: [PATCH 032/163] Preferences Window // New Layout. --- Source/Base.lproj/MainMenu.xib | 7 +- Source/Base.lproj/preferences.xib | 816 +++++++++++++++-------- Source/InputMethodController.mm | 9 + Source/en.lproj/preferences.strings | 108 ++- Source/zh-Hans.lproj/preferences.strings | 120 +++- Source/zh-Hant.lproj/preferences.strings | 118 +++- 6 files changed, 831 insertions(+), 347 deletions(-) diff --git a/Source/Base.lproj/MainMenu.xib b/Source/Base.lproj/MainMenu.xib index b834ac493..036cfeb9d 100644 --- a/Source/Base.lproj/MainMenu.xib +++ b/Source/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -11,7 +11,7 @@ - + @@ -288,6 +288,7 @@ + diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index be6e7530a..1cb0c9b46 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -16,283 +16,559 @@ - + - - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Item 1 - Item 2 - Item 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Item 1 + Item 2 + Item 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index abb03e3fa..1e398bdbb 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1676,10 +1676,19 @@ void LTLoadLanguageModel() void LTLoadUserLanguageModelFile() { + // Autofix: Ensure that there's a new line in the user language model file. + // NSString *lineBreak = @"\n"; + // NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:LTUserPhrasesDataPathCHT() append:YES]; + // [stream open]; + // NSData *strData = [lineBreak dataUsingEncoding:NSUTF8StringEncoding]; + // [stream write:(uint8_t *)[strData bytes] maxLength:[strData length]]; + // [stream close]; + gUserPhraseLanguageModelCHT.close(); gUserPhraseLanguageModelCHS.close(); bool resultCHT = gUserPhraseLanguageModelCHT.open([LTUserPhrasesDataPathCHT() UTF8String]); bool resultCHS = gUserPhraseLanguageModelCHS.open([LTUserPhrasesDataPathCHS() UTF8String]); + if (!resultCHT) { NSLog(@"Failed opening language model for CHT user phrases."); } diff --git a/Source/en.lproj/preferences.strings b/Source/en.lproj/preferences.strings index ff209dfac..76042fc34 100644 --- a/Source/en.lproj/preferences.strings +++ b/Source/en.lproj/preferences.strings @@ -1,12 +1,12 @@ -/* Class = "NSWindow"; title = "Bopomofo Preferences"; ObjectID = "1"; */ -"1.title" = "Bopomofo Preferences"; +/* Class = "NSWindow"; title = "vChewing Preferences"; ObjectID = "1"; */ +"1.title" = "vChewing Preferences"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "5"; */ "5.title" = "OtherViews"; /* Class = "NSMenuItem"; title = "Standard"; ObjectID = "6"; */ -"6.title" = "Standard"; +"6.title" = "Microsoft Standard / Daqian / Wang / 01"; /* Class = "NSMenuItem"; title = "ETen"; ObjectID = "7"; */ "7.title" = "ETen"; @@ -20,17 +20,17 @@ /* Class = "NSMenuItem"; title = "Hanyu Pinyin"; ObjectID = "10"; */ "10.title" = "Hanyu Pinyin"; -/* Class = "NSTextFieldCell"; title = "Bopomofo Keyboard Layout:"; ObjectID = "12"; */ -"12.title" = "Bopomofo Keyboard Layout:"; +/* Class = "NSTextFieldCell"; title = "Bopomofo:"; ObjectID = "12"; */ +"12.title" = "Bopomofo:"; -/* Class = "NSTextFieldCell"; title = "Show Candidate Phrase:"; ObjectID = "14"; */ -"14.title" = "Show Candidate Phrase:"; +/* Class = "NSTextFieldCell"; title = "Choose the cursor position where you want to list possible candidates."; ObjectID = "14"; */ +"14.title" = "Choose the cursor position where you want to list possible candidates."; -/* Class = "NSButtonCell"; title = "Before the cursor (like Hanin)"; ObjectID = "16"; */ -"16.title" = "Before the cursor (like Hanin)"; +/* Class = "NSButtonCell"; title = "Cursor to the front of the phrase (like Matsushita Hanin IME)"; ObjectID = "16"; */ +"16.title" = "Cursor to the front of the phrase (like Matsushita Hanin IME)"; -/* Class = "NSButtonCell"; title = "After the cursor (like MS IME)"; ObjectID = "17"; */ -"17.title" = "After the cursor (like MS IME)"; +/* Class = "NSButtonCell"; title = "Cursor to the rear of the phrase (like MS New-Phonetic IME)"; ObjectID = "17"; */ +"17.title" = "Cursor to the rear of the phrase (like MS New-Phonetic IME)"; /* Class = "NSButtonCell"; title = "Radio"; ObjectID = "18"; */ "18.title" = "Radio"; @@ -44,11 +44,11 @@ /* Class = "NSButtonCell"; title = "Vertical"; ObjectID = "22"; */ "22.title" = "Vertical"; -/* Class = "NSTextFieldCell"; title = "Candidate List Style:"; ObjectID = "24"; */ -"24.title" = "Candidate List Style:"; +/* Class = "NSTextFieldCell"; title = "Candidate List Layout:"; ObjectID = "24"; */ +"24.title" = "Candidate List Layout:"; -/* Class = "NSTextFieldCell"; title = "Candidate Text Size:"; ObjectID = "29"; */ -"29.title" = "Candidate Text Size:"; +/* Class = "NSTextFieldCell"; title = "Candidate UI font size:"; ObjectID = "29"; */ +"29.title" = "Candidate UI font size:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "92"; */ "92.title" = "OtherViews"; @@ -77,11 +77,11 @@ /* Class = "NSMenuItem"; title = "96"; ObjectID = "101"; */ "101.title" = "96"; -/* Class = "NSButtonCell"; title = "Space key chooses candidate"; ObjectID = "110"; */ -"110.title" = "Space key chooses candidate"; +/* Class = "NSButtonCell"; title = "Press Space key chooses candidate"; ObjectID = "110"; */ +"110.title" = "Press Space key chooses candidate"; -/* Class = "NSTextFieldCell"; title = "Alphanumeric Keyboard Layout:"; ObjectID = "126"; */ -"126.title" = "Alphanumeric Keyboard Layout:"; +/* Class = "NSTextFieldCell"; title = "Alphanumeric:"; ObjectID = "126"; */ +"126.title" = "Alphanumeric:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "128"; */ "128.title" = "OtherViews"; @@ -89,14 +89,71 @@ /* Class = "NSMenuItem"; title = "IBM"; ObjectID = "137"; */ "137.title" = "IBM"; +/* Class = "NSTabViewItem"; label = "Keyboard"; ObjectID = "1AW-xf-c2f"; */ +"1AW-xf-c2f.label" = "Keyboard"; + +/* Class = "NSBox"; title = "General Settings"; ObjectID = "2Y6-Am-WM1"; */ +"2Y6-Am-WM1.title" = "General Settings"; + +/* Class = "NSTextFieldCell"; title = "Choose which keys you prefer for selecting candidates."; ObjectID = "2pS-nv-te4"; */ +"2pS-nv-te4.title" = "Choose which keys you prefer for selecting candidates."; + +/* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ +"9DS-Rc-TXq.title" = "UI language setting:"; + +/* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ +"BSK-bH-Gct.title" = "Auto-convert traditional Chinese glyphs to KangXi characters"; + +/* Class = "NSBox"; title = "Advanced Settings"; ObjectID = "E1l-m8-xgb"; */ +"E1l-m8-xgb.title" = "Advanced Settings"; + +/* Class = "NSMenuItem"; title = "English"; ObjectID = "FSG-lN-CJO"; */ +"FSG-lN-CJO.title" = "English"; + /* Class = "NSTextFieldCell"; title = "Selection Keys:"; ObjectID = "FnD-oH-El5"; */ "FnD-oH-El5.title" = "Selection Keys:"; +/* Class = "NSMenuItem"; title = "Auto-Select"; ObjectID = "GlJ-Ns-9eE"; */ +"GlJ-Ns-9eE.title" = "Auto-Select"; + +/* Class = "NSTabViewItem"; label = "Dictionary"; ObjectID = "ISh-Da-hKv"; */ +"ISh-Da-hKv.label" = "Dictionary"; + +/* Class = "NSTabViewItem"; label = "General"; ObjectID = "QUQ-oY-4Hc"; */ +"QUQ-oY-4Hc.label" = "General"; + +/* Class = "NSTextFieldCell"; title = "Choose your preferred keyboard layout."; ObjectID = "RQ6-MS-m4C"; */ +"RQ6-MS-m4C.title" = "Choose your preferred keyboard layout."; + +/* Class = "NSMenuItem"; title = "Traditional Chinese"; ObjectID = "TXr-FF-ehw"; */ +"TXr-FF-ehw.title" = "Traditional Chinese"; + +/* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ +"Uyz-xL-TVN.title" = "Output Settings"; + +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (non-PUA UTF-8 / GB18030 characters only)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "Enable CNS11643 Support (non-PUA UTF-8 / GB18030 characters only)"; + +/* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ +"Wvt-HE-LOv.title" = "Keyboard Layout"; + /* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ "Z9t-P0-BLF.title" = "Check for updates automatically"; -/* Class = "NSButtonCell"; title = "ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ -"f2j-xD-4xK.title" = "ESC key clears entire input buffer"; +/* Class = "NSTextFieldCell"; title = "Change user interface language."; ObjectID = "ZEv-Q2-mYL"; */ +"ZEv-Q2-mYL.title" = "Change user interface language."; + +/* Class = "NSMenuItem"; title = "Simplified Chinese"; ObjectID = "akC-2g-ybz"; */ +"akC-2g-ybz.title" = "Simplified Chinese"; + +/* Class = "NSButtonCell"; title = "Press ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ +"f2j-xD-4xK.title" = "Press ESC key clears entire input buffer"; + +/* Class = "NSTextFieldCell"; title = "Change UI font size of candidate window for a better visual clarity."; ObjectID = "iRg-wx-Nx2"; */ +"iRg-wx-Nx2.title" = "Change UI font size of candidate window for a better visual clarity."; + +/* Class = "NSTextFieldCell"; title = "This dictionary page is under development."; ObjectID = "j48-5a-cEs"; */ +"j48-5a-cEs.title" = "This dictionary page is under development."; /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; @@ -106,3 +163,12 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; + +/* Class = "NSBox"; title = "Dictionary Settings"; ObjectID = "s4r-ji-vbr"; */ +"s4r-ji-vbr.title" = "Dictionary Settings"; + +/* Class = "NSTextFieldCell"; title = "Choose your preferred layout of the candidate window."; ObjectID = "xC5-yV-1W1"; */ +"xC5-yV-1W1.title" = "Choose your preferred layout of the candidate window."; + +/* Class = "NSTabViewItem"; label = "Advanced"; ObjectID = "xrE-8T-WKO"; */ +"xrE-8T-WKO.label" = "Advanced"; diff --git a/Source/zh-Hans.lproj/preferences.strings b/Source/zh-Hans.lproj/preferences.strings index 90cc93355..572b9b323 100644 --- a/Source/zh-Hans.lproj/preferences.strings +++ b/Source/zh-Hans.lproj/preferences.strings @@ -1,36 +1,36 @@ -/* Class = "NSWindow"; title = "Bopomofo Preferences"; ObjectID = "1"; */ -"1.title" = "注音偏好设定"; +/* Class = "NSWindow"; title = "vChewing Preferences"; ObjectID = "1"; */ +"1.title" = "威注音偏好设定"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "5"; */ "5.title" = "OtherViews"; /* Class = "NSMenuItem"; title = "Standard"; ObjectID = "6"; */ -"6.title" = "大千"; +"6.title" = "微软/大千/王安/国乔/零壹/仲鼎"; /* Class = "NSMenuItem"; title = "ETen"; ObjectID = "7"; */ "7.title" = "倚天传统"; /* Class = "NSMenuItem"; title = "Hsu"; ObjectID = "8"; */ -"8.title" = "许氏"; +"8.title" = "许氏(国音&自然)"; /* Class = "NSMenuItem"; title = "ETen26"; ObjectID = "9"; */ -"9.title" = "倚天二六"; +"9.title" = "倚天二十六键"; /* Class = "NSMenuItem"; title = "Hanyu Pinyin"; ObjectID = "10"; */ -"10.title" = "汉语拼音+数字标调"; +"10.title" = "汉语拼音二式(字母拼音+ 12345 数字标调)"; -/* Class = "NSTextFieldCell"; title = "Bopomofo Keyboard Layout:"; ObjectID = "12"; */ -"12.title" = "注音键盘布局:"; +/* Class = "NSTextFieldCell"; title = "Bopomofo:"; ObjectID = "12"; */ +"12.title" = "注音键盘布局:"; -/* Class = "NSTextFieldCell"; title = "Show Candidate Phrase:"; ObjectID = "14"; */ -"14.title" = "用于显示候选词的游标定位:"; +/* Class = "NSTextFieldCell"; title = "Choose the cursor position where you want to list possible candidates."; ObjectID = "14"; */ +"14.title" = "用以触发选字的游标相对位置:"; -/* Class = "NSButtonCell"; title = "Before the cursor (like Hanin)"; ObjectID = "16"; */ -"16.title" = "游标前置(像松下汉音输入法)"; +/* Class = "NSButtonCell"; title = "Cursor to the front of the phrase (like Matsushita Hanin IME)"; ObjectID = "16"; */ +"16.title" = "游标至于词语前方 // 松下汉音风格"; -/* Class = "NSButtonCell"; title = "After the cursor (like MS IME)"; ObjectID = "17"; */ -"17.title" = "游标后置(像微软新注音)"; +/* Class = "NSButtonCell"; title = "Cursor to the rear of the phrase (like MS New-Phonetic IME)"; ObjectID = "17"; */ +"17.title" = "游标至于词语后方 // 微软新注音风格"; /* Class = "NSButtonCell"; title = "Radio"; ObjectID = "18"; */ "18.title" = "Radio"; @@ -39,16 +39,16 @@ "20.title" = "Radio"; /* Class = "NSButtonCell"; title = "Horizontal"; ObjectID = "21"; */ -"21.title" = "横向"; +"21.title" = "横向布局"; /* Class = "NSButtonCell"; title = "Vertical"; ObjectID = "22"; */ -"22.title" = "纵向"; +"22.title" = "纵向布局"; -/* Class = "NSTextFieldCell"; title = "Candidate List Style:"; ObjectID = "24"; */ -"24.title" = "候选词窗格排版:"; +/* Class = "NSTextFieldCell"; title = "Candidate List Layout:"; ObjectID = "24"; */ +"24.title" = "候选字窗布局:"; -/* Class = "NSTextFieldCell"; title = "Candidate Text Size:"; ObjectID = "29"; */ -"29.title" = "候选词窗格字型大小:"; +/* Class = "NSTextFieldCell"; title = "Candidate UI font size:"; ObjectID = "29"; */ +"29.title" = "字型大小设定:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "92"; */ "92.title" = "OtherViews"; @@ -77,11 +77,11 @@ /* Class = "NSMenuItem"; title = "96"; ObjectID = "101"; */ "101.title" = "96"; -/* Class = "NSButtonCell"; title = "Space key chooses candidate"; ObjectID = "110"; */ -"110.title" = "摁空格键以选取候选词"; +/* Class = "NSButtonCell"; title = "Press Space key chooses candidate"; ObjectID = "110"; */ +"110.title" = "敲空格键以选字"; -/* Class = "NSTextFieldCell"; title = "Alphanumeric Keyboard Layout:"; ObjectID = "126"; */ -"126.title" = "英数键盘布局:"; +/* Class = "NSTextFieldCell"; title = "Alphanumeric:"; ObjectID = "126"; */ +"126.title" = "英数键盘布局:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "128"; */ "128.title" = "OtherViews"; @@ -89,15 +89,72 @@ /* Class = "NSMenuItem"; title = "IBM"; ObjectID = "137"; */ "137.title" = "IBM"; +/* Class = "NSTabViewItem"; label = "Keyboard"; ObjectID = "1AW-xf-c2f"; */ +"1AW-xf-c2f.label" = "键盘"; + +/* Class = "NSBox"; title = "General Settings"; ObjectID = "2Y6-Am-WM1"; */ +"2Y6-Am-WM1.title" = "一般设定"; + +/* Class = "NSTextFieldCell"; title = "Choose which keys you prefer for selecting candidates."; ObjectID = "2pS-nv-te4"; */ +"2pS-nv-te4.title" = "选择您所偏好的用来选字的按键组合。"; + +/* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ +"9DS-Rc-TXq.title" = "介面语言设定:"; + +/* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ +"BSK-bH-Gct.title" = "自动将繁体中文字转换为康熙字"; + +/* Class = "NSBox"; title = "Advanced Settings"; ObjectID = "E1l-m8-xgb"; */ +"E1l-m8-xgb.title" = "进阶设定"; + +/* Class = "NSMenuItem"; title = "English"; ObjectID = "FSG-lN-CJO"; */ +"FSG-lN-CJO.title" = "英文"; + /* Class = "NSTextFieldCell"; title = "Selection Keys:"; ObjectID = "FnD-oH-El5"; */ -"FnD-oH-El5.title" = "选字键:"; +"FnD-oH-El5.title" = "选字键:"; + +/* Class = "NSMenuItem"; title = "Auto-Select"; ObjectID = "GlJ-Ns-9eE"; */ +"GlJ-Ns-9eE.title" = "自动选择"; + +/* Class = "NSTabViewItem"; label = "Dictionary"; ObjectID = "ISh-Da-hKv"; */ +"ISh-Da-hKv.label" = "辞典"; + +/* Class = "NSTabViewItem"; label = "General"; ObjectID = "QUQ-oY-4Hc"; */ +"QUQ-oY-4Hc.label" = "一般"; + +/* Class = "NSTextFieldCell"; title = "Choose your preferred keyboard layout."; ObjectID = "RQ6-MS-m4C"; */ +"RQ6-MS-m4C.title" = "选择您所偏好的键盘布局。"; + +/* Class = "NSMenuItem"; title = "Traditional Chinese"; ObjectID = "TXr-FF-ehw"; */ +"TXr-FF-ehw.title" = "繁体中文"; + +/* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ +"Uyz-xL-TVN.title" = "输出设定"; + +/* Class = "NSButtonCell"; title = "Enable partial CNS11643 Support (non-PUA UTF-8 characters only)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "启用部分 CNS11643 全字库支援 (仅限非 PUA 区域的 UTF-8 文字)"; + +/* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ +"Wvt-HE-LOv.title" = "键盘布局"; /* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ -"Z9t-P0-BLF.title" = "自动检查软件更新"; +"Z9t-P0-BLF.title" = "自动检查软体更新"; + +/* Class = "NSTextFieldCell"; title = "Change user interface language."; ObjectID = "ZEv-Q2-mYL"; */ +"ZEv-Q2-mYL.title" = "变更使用者介面语言。"; + +/* Class = "NSMenuItem"; title = "Simplified Chinese"; ObjectID = "akC-2g-ybz"; */ +"akC-2g-ybz.title" = "简体中文"; -/* Class = "NSButtonCell"; title = "ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ +/* Class = "NSButtonCell"; title = "Press ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ "f2j-xD-4xK.title" = "敲 ESC 键以清空整个输入缓冲区"; +/* Class = "NSTextFieldCell"; title = "Change UI font size of candidate window for a better visual clarity."; ObjectID = "iRg-wx-Nx2"; */ +"iRg-wx-Nx2.title" = "变更候选字窗的字型大小。"; + +/* Class = "NSTextFieldCell"; title = "This dictionary page is under development."; ObjectID = "j48-5a-cEs"; */ +"j48-5a-cEs.title" = "该辞典页面相关功能正在施工。"; + /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; @@ -106,3 +163,12 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; + +/* Class = "NSBox"; title = "Dictionary Settings"; ObjectID = "s4r-ji-vbr"; */ +"s4r-ji-vbr.title" = "辞典设定"; + +/* Class = "NSTextFieldCell"; title = "Choose your preferred layout of the candidate window."; ObjectID = "xC5-yV-1W1"; */ +"xC5-yV-1W1.title" = "选择您所偏好的候选字窗布局。"; + +/* Class = "NSTabViewItem"; label = "Advanced"; ObjectID = "xrE-8T-WKO"; */ +"xrE-8T-WKO.label" = "进阶"; diff --git a/Source/zh-Hant.lproj/preferences.strings b/Source/zh-Hant.lproj/preferences.strings index 05de70d85..a43aa0754 100644 --- a/Source/zh-Hant.lproj/preferences.strings +++ b/Source/zh-Hant.lproj/preferences.strings @@ -1,36 +1,36 @@ -/* Class = "NSWindow"; title = "Bopomofo Preferences"; ObjectID = "1"; */ -"1.title" = "注音偏好設定"; +/* Class = "NSWindow"; title = "vChewing Preferences"; ObjectID = "1"; */ +"1.title" = "威注音偏好設定"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "5"; */ "5.title" = "OtherViews"; /* Class = "NSMenuItem"; title = "Standard"; ObjectID = "6"; */ -"6.title" = "大千"; +"6.title" = "微軟/大千/王安/國喬/零壹/仲鼎"; /* Class = "NSMenuItem"; title = "ETen"; ObjectID = "7"; */ "7.title" = "倚天傳統"; /* Class = "NSMenuItem"; title = "Hsu"; ObjectID = "8"; */ -"8.title" = "許氏"; +"8.title" = "許氏(國音&自然)"; /* Class = "NSMenuItem"; title = "ETen26"; ObjectID = "9"; */ -"9.title" = "倚天二六"; +"9.title" = "倚天二十六鍵"; /* Class = "NSMenuItem"; title = "Hanyu Pinyin"; ObjectID = "10"; */ -"10.title" = "漢語拼音+數字標調"; +"10.title" = "漢語拼音二式(字母拼音+ 12345 數字標調)"; -/* Class = "NSTextFieldCell"; title = "Bopomofo Keyboard Layout:"; ObjectID = "12"; */ -"12.title" = "注音鍵盤佈局:"; +/* Class = "NSTextFieldCell"; title = "Bopomofo:"; ObjectID = "12"; */ +"12.title" = "注音鍵盤佈局:"; -/* Class = "NSTextFieldCell"; title = "Show Candidate Phrase:"; ObjectID = "14"; */ -"14.title" = "用於顯示候選詞的游標定位:"; +/* Class = "NSTextFieldCell"; title = "Choose the cursor position where you want to list possible candidates."; ObjectID = "14"; */ +"14.title" = "用以觸發選字的游標相對位置:"; -/* Class = "NSButtonCell"; title = "Before the cursor (like Hanin)"; ObjectID = "16"; */ -"16.title" = "游標前置(像松下漢音輸入法)"; +/* Class = "NSButtonCell"; title = "Cursor to the front of the phrase (like Matsushita Hanin IME)"; ObjectID = "16"; */ +"16.title" = "游標至於詞語前方 // 松下漢音風格"; -/* Class = "NSButtonCell"; title = "After the cursor (like MS IME)"; ObjectID = "17"; */ -"17.title" = "游標後置(像微軟新注音)"; +/* Class = "NSButtonCell"; title = "Cursor to the rear of the phrase (like MS New-Phonetic IME)"; ObjectID = "17"; */ +"17.title" = "游標至於詞語後方 // 微軟新注音風格"; /* Class = "NSButtonCell"; title = "Radio"; ObjectID = "18"; */ "18.title" = "Radio"; @@ -39,16 +39,16 @@ "20.title" = "Radio"; /* Class = "NSButtonCell"; title = "Horizontal"; ObjectID = "21"; */ -"21.title" = "橫向"; +"21.title" = "橫向佈局"; /* Class = "NSButtonCell"; title = "Vertical"; ObjectID = "22"; */ -"22.title" = "縱向"; +"22.title" = "縱向佈局"; -/* Class = "NSTextFieldCell"; title = "Candidate List Style:"; ObjectID = "24"; */ -"24.title" = "候選詞窗格排版:"; +/* Class = "NSTextFieldCell"; title = "Candidate List Layout:"; ObjectID = "24"; */ +"24.title" = "候選字窗佈局:"; -/* Class = "NSTextFieldCell"; title = "Candidate Text Size:"; ObjectID = "29"; */ -"29.title" = "候選詞窗格字型大小:"; +/* Class = "NSTextFieldCell"; title = "Candidate UI font size:"; ObjectID = "29"; */ +"29.title" = "字型大小設定:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "92"; */ "92.title" = "OtherViews"; @@ -77,11 +77,11 @@ /* Class = "NSMenuItem"; title = "96"; ObjectID = "101"; */ "101.title" = "96"; -/* Class = "NSButtonCell"; title = "Space key chooses candidate"; ObjectID = "110"; */ -"110.title" = "摁空格鍵以選取候選詞"; +/* Class = "NSButtonCell"; title = "Press Space key chooses candidate"; ObjectID = "110"; */ +"110.title" = "敲空格鍵以選字"; -/* Class = "NSTextFieldCell"; title = "Alphanumeric Keyboard Layout:"; ObjectID = "126"; */ -"126.title" = "英數鍵盤佈局:"; +/* Class = "NSTextFieldCell"; title = "Alphanumeric:"; ObjectID = "126"; */ +"126.title" = "英數鍵盤佈局:"; /* Class = "NSMenu"; title = "OtherViews"; ObjectID = "128"; */ "128.title" = "OtherViews"; @@ -89,15 +89,72 @@ /* Class = "NSMenuItem"; title = "IBM"; ObjectID = "137"; */ "137.title" = "IBM"; +/* Class = "NSTabViewItem"; label = "Keyboard"; ObjectID = "1AW-xf-c2f"; */ +"1AW-xf-c2f.label" = "鍵盤"; + +/* Class = "NSBox"; title = "General Settings"; ObjectID = "2Y6-Am-WM1"; */ +"2Y6-Am-WM1.title" = "一般設定"; + +/* Class = "NSTextFieldCell"; title = "Choose which keys you prefer for selecting candidates."; ObjectID = "2pS-nv-te4"; */ +"2pS-nv-te4.title" = "選擇您所偏好的用來選字的按鍵組合。"; + +/* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ +"9DS-Rc-TXq.title" = "介面語言設定:"; + +/* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ +"BSK-bH-Gct.title" = "自動將繁體中文字轉換為康熙字"; + +/* Class = "NSBox"; title = "Advanced Settings"; ObjectID = "E1l-m8-xgb"; */ +"E1l-m8-xgb.title" = "進階設定"; + +/* Class = "NSMenuItem"; title = "English"; ObjectID = "FSG-lN-CJO"; */ +"FSG-lN-CJO.title" = "英文"; + /* Class = "NSTextFieldCell"; title = "Selection Keys:"; ObjectID = "FnD-oH-El5"; */ -"FnD-oH-El5.title" = "選字鍵:"; +"FnD-oH-El5.title" = "選字鍵:"; + +/* Class = "NSMenuItem"; title = "Auto-Select"; ObjectID = "GlJ-Ns-9eE"; */ +"GlJ-Ns-9eE.title" = "自動選擇"; + +/* Class = "NSTabViewItem"; label = "Dictionary"; ObjectID = "ISh-Da-hKv"; */ +"ISh-Da-hKv.label" = "辭典"; + +/* Class = "NSTabViewItem"; label = "General"; ObjectID = "QUQ-oY-4Hc"; */ +"QUQ-oY-4Hc.label" = "一般"; + +/* Class = "NSTextFieldCell"; title = "Choose your preferred keyboard layout."; ObjectID = "RQ6-MS-m4C"; */ +"RQ6-MS-m4C.title" = "選擇您所偏好的鍵盤佈局。"; + +/* Class = "NSMenuItem"; title = "Traditional Chinese"; ObjectID = "TXr-FF-ehw"; */ +"TXr-FF-ehw.title" = "繁體中文"; + +/* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ +"Uyz-xL-TVN.title" = "輸出設定"; + +/* Class = "NSButtonCell"; title = "Enable partial CNS11643 Support (non-PUA UTF-8 characters only)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "啟用部分 CNS11643 全字庫支援 (僅限非 PUA 區域的 UTF-8 文字)"; + +/* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ +"Wvt-HE-LOv.title" = "鍵盤佈局"; /* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ "Z9t-P0-BLF.title" = "自動檢查軟體更新"; -/* Class = "NSButtonCell"; title = "ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ +/* Class = "NSTextFieldCell"; title = "Change user interface language."; ObjectID = "ZEv-Q2-mYL"; */ +"ZEv-Q2-mYL.title" = "變更使用者介面語言。"; + +/* Class = "NSMenuItem"; title = "Simplified Chinese"; ObjectID = "akC-2g-ybz"; */ +"akC-2g-ybz.title" = "簡體中文"; + +/* Class = "NSButtonCell"; title = "Press ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ "f2j-xD-4xK.title" = "敲 ESC 鍵以清空整個輸入緩衝區"; +/* Class = "NSTextFieldCell"; title = "Change UI font size of candidate window for a better visual clarity."; ObjectID = "iRg-wx-Nx2"; */ +"iRg-wx-Nx2.title" = "變更候選字窗的字型大小。"; + +/* Class = "NSTextFieldCell"; title = "This dictionary page is under development."; ObjectID = "j48-5a-cEs"; */ +"j48-5a-cEs.title" = "該辭典頁面相關功能正在施工。"; + /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; @@ -106,3 +163,12 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; + +/* Class = "NSBox"; title = "Dictionary Settings"; ObjectID = "s4r-ji-vbr"; */ +"s4r-ji-vbr.title" = "辭典設定"; + +/* Class = "NSTextFieldCell"; title = "Choose your preferred layout of the candidate window."; ObjectID = "xC5-yV-1W1"; */ +"xC5-yV-1W1.title" = "選擇您所偏好的候選字窗佈局。"; + +/* Class = "NSTabViewItem"; label = "Advanced"; ObjectID = "xrE-8T-WKO"; */ +"xrE-8T-WKO.label" = "進階"; -- Gitee From ab3f5bcb9c1ea73f06977467374344282b50cb8b Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 12 Jan 2022 13:11:26 +0800 Subject: [PATCH 033/163] Shiki #15 FastLM Patch // + \n to the end of LM data if not exist. --- Source/Engine/FastLM.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Source/Engine/FastLM.cpp b/Source/Engine/FastLM.cpp index 48569c65d..7d759c7ab 100644 --- a/Source/Engine/FastLM.cpp +++ b/Source/Engine/FastLM.cpp @@ -31,6 +31,7 @@ #include #include #include +#include using namespace Formosa::Gramambular; @@ -53,7 +54,23 @@ bool FastLM::open(const char *path) if (data) { return false; } - + + fstream zfd(path); + zfd.seekg(-1,ios_base::end); + char z; + zfd.get(z); + if(z!='\n'){ + syslog(LOG_CONS, "REPORT: File is not ended with a new line.\n"); + syslog(LOG_CONS, "PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); + ofstream zfdo(path, std::ios_base::app); + zfdo << std::endl; + zfdo.close(); + if (zfdo.fail()) { + syslog(LOG_CONS, "REPORT: Failed to append a newline to the data file. Insufficient Privileges?\n"); + return false; + } + } + fd = ::open(path, O_RDONLY); if (fd == -1) { return false; -- Gitee From 79fcdb3ec008664511eb97a55634caf3c06e7a5a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 13 Jan 2022 10:24:05 +0800 Subject: [PATCH 034/163] Zonble: Swiftify // Voltaire. --- .../HorizontalCandidateController.swift | 418 ++++++++++++++++ Source/CandidateUI/VTCandidateController.h | 68 --- Source/CandidateUI/VTCandidateController.m | 178 ------- .../CandidateUI/VTCandidateController.swift | 152 ++++++ .../VTHorizontalCandidateController.h | 45 -- .../VTHorizontalCandidateController.m | 267 ---------- .../CandidateUI/VTHorizontalCandidateView.h | 59 --- .../CandidateUI/VTHorizontalCandidateView.m | 239 --------- .../VTVerticalCandidateController.h | 41 -- .../VTVerticalCandidateController.m | 469 ------------------ .../VTVerticalCandidateTableView.h | 31 -- .../VTVerticalCandidateTableView.m | 38 -- .../CandidateUI/VTVerticalKeyLabelStripView.h | 43 -- .../CandidateUI/VTVerticalKeyLabelStripView.m | 114 ----- .../VerticalCandidateController.swift | 455 +++++++++++++++++ Source/InputMethodController.mm | 2 - vChewing.xcodeproj/project.pbxproj | 48 +- 17 files changed, 1037 insertions(+), 1630 deletions(-) create mode 100644 Source/CandidateUI/HorizontalCandidateController.swift delete mode 100644 Source/CandidateUI/VTCandidateController.h delete mode 100644 Source/CandidateUI/VTCandidateController.m create mode 100644 Source/CandidateUI/VTCandidateController.swift delete mode 100644 Source/CandidateUI/VTHorizontalCandidateController.h delete mode 100644 Source/CandidateUI/VTHorizontalCandidateController.m delete mode 100644 Source/CandidateUI/VTHorizontalCandidateView.h delete mode 100644 Source/CandidateUI/VTHorizontalCandidateView.m delete mode 100644 Source/CandidateUI/VTVerticalCandidateController.h delete mode 100644 Source/CandidateUI/VTVerticalCandidateController.m delete mode 100644 Source/CandidateUI/VTVerticalCandidateTableView.h delete mode 100644 Source/CandidateUI/VTVerticalCandidateTableView.m delete mode 100644 Source/CandidateUI/VTVerticalKeyLabelStripView.h delete mode 100644 Source/CandidateUI/VTVerticalKeyLabelStripView.m create mode 100644 Source/CandidateUI/VerticalCandidateController.swift diff --git a/Source/CandidateUI/HorizontalCandidateController.swift b/Source/CandidateUI/HorizontalCandidateController.swift new file mode 100644 index 000000000..74bd033d9 --- /dev/null +++ b/Source/CandidateUI/HorizontalCandidateController.swift @@ -0,0 +1,418 @@ +// +// HorizontalCandidateController.swift +// +// Copyright (c) 2011 The McBopomofo Project. +// +// Contributors: +// Mengjuei Hsieh (@mjhsieh) +// Weizhong Yang (@zonble) +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +fileprivate class HorizontalCandidateView: NSView { + var highlightedIndex: UInt = 0 + var action: Selector? + weak var target: AnyObject? + + private var keyLabels: [String] = [] + private var displayedCandidates: [String] = [] + private var keyLabelHeight: CGFloat = 0 + private var candidateTextHeight: CGFloat = 0 + private var cellPadding: CGFloat = 0 + private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var elementWidths: [CGFloat] = [] + private var trackingHighlightedIndex: UInt = UInt.max + + override var isFlipped: Bool { + true + } + + var sizeForView: NSSize { + var result = NSSize.zero + + if !elementWidths.isEmpty { + result.width = elementWidths.reduce(0, +) + result.width += CGFloat(elementWidths.count) + result.height = keyLabelHeight + candidateTextHeight + 1.0 + } + return result + } + + @objc (setKeyLabels:displayedCandidates:) + func set(keyLabels labels: [String], displayedCandidates candidates: [String]) { + let count = min(labels.count, candidates.count) + keyLabels = Array(labels[0.. UInt? { + let location = convert(event.locationInWindow, to: nil) + if !NSPointInRect(location, self.bounds) { + return nil + } + var accuWidth: CGFloat = 0.0 + for index in 0..= accuWidth && location.x <= accuWidth + currentWidth { + return UInt(index) + } + accuWidth += currentWidth + 1.0 + } + return nil + + } + + override func mouseUp(with event: NSEvent) { + trackingHighlightedIndex = highlightedIndex + guard let newIndex = findHitIndex(event: event) else { + return + } + highlightedIndex = newIndex + self.setNeedsDisplay(self.bounds) + } + + override func mouseDown(with event: NSEvent) { + guard let newIndex = findHitIndex(event: event) else { + return + } + var triggerAction = false + if newIndex == highlightedIndex { + triggerAction = true + } else { + highlightedIndex = trackingHighlightedIndex + } + + trackingHighlightedIndex = 0 + self.setNeedsDisplay(self.bounds) + if triggerAction { + if let target = target as? NSObject, let action = action { + target.perform(action, with: self) + } + } + } +} + +@objc (VTHorizontalCandidateController) +public class HorizontalCandidateController: CandidateController { + private var candidateView: HorizontalCandidateView + private var prevPageButton: NSButton + private var nextPageButton: NSButton + private var currentPage: UInt = 0 + + public init() { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] + let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) + panel.hasShadow = true + + contentRect.origin = NSPoint.zero + candidateView = HorizontalCandidateView(frame: contentRect) + panel.contentView?.addSubview(candidateView) + + contentRect.size = NSSize(width: 36.0, height: 20.0) + nextPageButton = NSButton(frame: contentRect) + nextPageButton.setButtonType(.momentaryLight) + nextPageButton.bezelStyle = .smallSquare + nextPageButton.title = "»" + + prevPageButton = NSButton(frame: contentRect) + prevPageButton.setButtonType(.momentaryLight) + prevPageButton.bezelStyle = .smallSquare + prevPageButton.title = "«" + + panel.contentView?.addSubview(nextPageButton) + panel.contentView?.addSubview(prevPageButton) + + super.init(window: panel) + + candidateView.target = self + candidateView.action = #selector(candidateViewMouseDidClick(_:)) + + nextPageButton.target = self + nextPageButton.action = #selector(pageButtonAction(_:)) + + prevPageButton.target = self + prevPageButton.action = #selector(pageButtonAction(_:)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func reloadData() { + candidateView.highlightedIndex = 0 + currentPage = 0 + layoutCandidateView() + } + + public override func showNextPage() -> Bool { + guard delegate != nil else { + return false + } + + if currentPage + 1 >= pageCount { + return false + } + + currentPage += 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } + + public override func showPreviousPage() -> Bool { + guard delegate != nil else { + return false + } + + if currentPage == 0 { + return false + } + + currentPage -= 1 + candidateView.highlightedIndex = 0 + layoutCandidateView() + return true + } + + public override func highlightNextCandidate() -> Bool { + guard let delegate = delegate else { + return false + } + + let currentIndex = selectedCandidateIndex + if currentIndex + 1 >= delegate.candidateCountForController(self) { + return false + } + selectedCandidateIndex = currentIndex + 1 + return true + } + + public override func highlightPreviousCandidate() -> Bool { + guard delegate != nil else { + return false + } + + let currentIndex = selectedCandidateIndex + if currentIndex == 0 { + return false + } + + selectedCandidateIndex = currentIndex - 1 + return true + } + + public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + guard let delegate = delegate else { + return UInt.max + } + + let result = currentPage * UInt(keyLabels.count) + index + return result < delegate.candidateCountForController(self) ? result : UInt.max + } + + public override var selectedCandidateIndex: UInt { + get { + currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex + } + set { + guard let delegate = delegate else { + return + } + let keyLabelCount = UInt(keyLabels.count) + if newValue < delegate.candidateCountForController(self) { + currentPage = newValue / keyLabelCount + candidateView.highlightedIndex = newValue % keyLabelCount + layoutCandidateView() + } + } + } +} + +extension HorizontalCandidateController { + + private var pageCount: UInt { + guard let delegate = delegate else { + return 0 + } + let totalCount = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) + return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) + } + + private func layoutCandidateView() { + guard let delegate = delegate else { + return + } + + candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) + var candidates = [String]() + let count = delegate.candidateCountForController(self) + let keyLabelCount = UInt(keyLabels.count) + + let begin = currentPage * keyLabelCount + for index in begin.. 1 { + var buttonRect = nextPageButton.frame + var spacing = 0.0 + + if newSize.height < 40.0 { + buttonRect.size.height = floor(newSize.height / 2) + } else { + buttonRect.size.height = 20.0 + } + + if newSize.height >= 60.0 { + spacing = ceil(newSize.height * 0.1) + } + + let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0 + buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY) + nextPageButton.frame = buttonRect + + buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY + buttonRect.size.height + spacing) + prevPageButton.frame = buttonRect + + newSize.width += 52.0 + nextPageButton.isHidden = false + prevPageButton.isHidden = false + } else { + nextPageButton.isHidden = true + prevPageButton.isHidden = true + } + + frameRect = window?.frame ?? NSRect.zero + + let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height) + frameRect.size = newSize + frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) + self.window?.setFrame(frameRect, display: false) + candidateView.setNeedsDisplay(candidateView.bounds) + } + + @objc fileprivate func pageButtonAction(_ sender: Any) { + guard let sender = sender as? NSButton else { + return + } + if sender == nextPageButton { + _ = showNextPage() + } else if sender == prevPageButton { + _ = showPreviousPage() + } + } + + @objc fileprivate func candidateViewMouseDidClick(_ sender: Any) { + delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex) + } + +} diff --git a/Source/CandidateUI/VTCandidateController.h b/Source/CandidateUI/VTCandidateController.h deleted file mode 100644 index 41e36a5cb..000000000 --- a/Source/CandidateUI/VTCandidateController.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// VTCandidateController.h -// -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import - -@class VTCandidateController; - -@protocol VTCandidateControllerDelegate -- (NSUInteger)candidateCountForController:(VTCandidateController *)controller; -- (NSString *)candidateController:(VTCandidateController *)controller candidateAtIndex:(NSUInteger)index; -- (void)candidateController:(VTCandidateController *)controller didSelectCandidateAtIndex:(NSUInteger)index; -@end - -@interface VTCandidateController : NSWindowController -{ -@protected - __weak id _delegate; - NSArray *_keyLabels; - NSFont *_keyLabelFont; - NSFont *_candidateFont; - BOOL _visible; -} - -- (void)reloadData; - -- (BOOL)showNextPage; -- (BOOL)showPreviousPage; -- (BOOL)highlightNextCandidate; -- (BOOL)highlightPreviousCandidate; - -- (void)setWindowTopLeftPoint:(NSPoint)topLeftPoint bottomOutOfScreenAdjustmentHeight:(CGFloat)height; - -- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index; - -@property (weak, nonatomic) id delegate; -@property (assign, nonatomic) NSUInteger selectedCandidateIndex; - -@property (assign, nonatomic) BOOL visible; -@property (assign, nonatomic) NSPoint windowTopLeftPoint; - -@property (copy, nonatomic) NSArray *keyLabels; -@property (copy, nonatomic) NSFont *keyLabelFont; -@property (copy, nonatomic) NSFont *candidateFont; -@end diff --git a/Source/CandidateUI/VTCandidateController.m b/Source/CandidateUI/VTCandidateController.m deleted file mode 100644 index 3b5f700a7..000000000 --- a/Source/CandidateUI/VTCandidateController.m +++ /dev/null @@ -1,178 +0,0 @@ -// -// VTCandidateController.m -// -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "VTCandidateController.h" - - -@implementation VTCandidateController -@synthesize delegate = _delegate; -@synthesize keyLabels = _keyLabels; -@synthesize keyLabelFont = _keyLabelFont; -@synthesize candidateFont = _candidateFont; - -- (void)dealloc -{ - _keyLabels = nil; - _keyLabelFont = nil; - _candidateFont = nil; -} - -- (id)initWithWindow:(NSWindow *)window -{ - self = [super initWithWindow:window]; - if (self) { - // populate the default values - _keyLabels = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"]; - _keyLabelFont = [NSFont monospacedDigitSystemFontOfSize:14.0 weight:NSFontWeightMedium]; - _candidateFont = [NSFont systemFontOfSize:18.0 weight:NSFontWeightRegular]; - } - return self; -} - -- (void)reloadData -{ -} - -- (BOOL)showNextPage -{ - return NO; -} - -- (BOOL)showPreviousPage -{ - return NO; -} - -- (BOOL)highlightNextCandidate -{ - return NO; -} - -- (BOOL)highlightPreviousCandidate -{ - return NO; -} - -- (void)setWindowTopLeftPoint:(NSPoint)topLeftPoint bottomOutOfScreenAdjustmentHeight:(CGFloat)height -{ - // Since layout is now deferred, the origin setting should also be deferred so that - // the correct visible frame dimensions are used. - NSArray *params = [NSArray arrayWithObjects:[NSValue valueWithPoint:topLeftPoint], [NSNumber numberWithDouble:height], nil]; - [self performSelector:@selector(deferredSetWindowTopLeftPoint:) withObject:params afterDelay:0.0]; -} - -- (void)deferredSetWindowTopLeftPoint:(NSArray *)params -{ - NSPoint topLeftPoint = [[params objectAtIndex:0] pointValue]; - CGFloat height = [[params objectAtIndex:1] doubleValue]; - - NSPoint adjustedPoint = topLeftPoint; - CGFloat adjustedHeight = height; - - // first, locate the screen the point is in - NSRect screenFrame = [[NSScreen mainScreen] visibleFrame]; - - for (NSScreen *screen in [NSScreen screens]) { - NSRect frame = [screen visibleFrame]; - if (topLeftPoint.x >= NSMinX(frame) && topLeftPoint.x <= NSMaxX(frame) && topLeftPoint.y >= NSMinY(frame) && topLeftPoint.y <= NSMaxY(frame)) { - screenFrame = frame; - break; - } - } - - // make sure we don't have any erratic value - if (adjustedHeight > screenFrame.size.height / 2.0) { - adjustedHeight = 0.0; - } - - NSSize windowSize = [[self window] frame].size; - - // bottom beneath the screen? - if (adjustedPoint.y - windowSize.height < NSMinY(screenFrame)) { - adjustedPoint.y = topLeftPoint.y + adjustedHeight + windowSize.height; - } - - // top over the screen? - if (adjustedPoint.y >= NSMaxY(screenFrame)) { - adjustedPoint.y = NSMaxY(screenFrame) - 1.0; - } - - // right - if (adjustedPoint.x + windowSize.width >= NSMaxX(screenFrame)) { - adjustedPoint.x = NSMaxX(screenFrame) - windowSize.width; - } - - // left - if (adjustedPoint.x < NSMinX(screenFrame)) { - adjustedPoint.x = NSMinX(screenFrame); - } - - [[self window] setFrameTopLeftPoint:adjustedPoint]; -} - -- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index -{ - return NSUIntegerMax; -} - -- (BOOL)visible -{ - // Because setVisible: defers its action, we need to use our own visible. Do not use [[self window] isVisible]. - return _visible; -} - -- (void)setVisible:(BOOL)visible -{ - _visible = visible; - if (visible) { - [[self window] performSelector:@selector(orderFront:) withObject:self afterDelay:0.0]; - } - else { - [[self window] performSelector:@selector(orderOut:) withObject:self afterDelay:0.0]; - } -} - -- (NSPoint)windowTopLeftPoint -{ - NSRect frameRect = [[self window] frame]; - return NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height); -} - -- (void)setWindowTopLeftPoint:(NSPoint)topLeftPoint -{ - [self setWindowTopLeftPoint:topLeftPoint bottomOutOfScreenAdjustmentHeight:0.0]; -} - -- (NSUInteger)selectedCandidateIndex -{ - return NSUIntegerMax; -} - -- (void)setSelectedCandidateIndex:(NSUInteger)newIndex -{ -} -@end diff --git a/Source/CandidateUI/VTCandidateController.swift b/Source/CandidateUI/VTCandidateController.swift new file mode 100644 index 000000000..42a575f65 --- /dev/null +++ b/Source/CandidateUI/VTCandidateController.swift @@ -0,0 +1,152 @@ +// +// VTCandidateController.swift +// +// Voltaire IME Candidate Controller Module +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla // Original Developer +// Weizhong Yang (@zonble) @ OpenVanilla // Rewriter to Swift +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +@objc (VTCandidateControllerDelegate) +public protocol CandidateControllerDelegate: AnyObject { + func candidateCountForController(_ controller: CandidateController) -> UInt + func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String + func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) +} + +@objc (VTCandidateController) +public class CandidateController: NSWindowController { + @objc public weak var delegate: CandidateControllerDelegate? + @objc public var selectedCandidateIndex: UInt = UInt.max + @objc public var visible: Bool = false { + didSet { + if visible { + window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0) + } else { + window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0) + } + } + } + @objc public var windowTopLeftPoint: NSPoint { + get { + guard let frameRect = window?.frame else { + return NSPoint.zero + } + return NSPoint(x: frameRect.minX, y: frameRect.maxY) + } + set { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { + self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0) + } + } + } + + @objc public var keyLabels: [String] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] + @objc public var keyLabelFont: NSFont = NSFont.systemFont(ofSize: 14) + @objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18) + + @objc public func reloadData() { + } + + @objc public func showNextPage() -> Bool { + false + } + + @objc public func showPreviousPage() -> Bool { + false + } + + @objc public func highlightNextCandidate() -> Bool { + false + } + + @objc public func highlightPreviousCandidate() -> Bool { + false + } + + @objc public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + UInt.max + } + + @objc (setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) + public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { + self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height) + } + } + + func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { + var adjustedPoint = windowTopLeftPoint + var adjustedHeight = height + + var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero + for screen in NSScreen.screens { + let frame = screen.visibleFrame + if windowTopLeftPoint.x >= frame.minX && + windowTopLeftPoint.x <= frame.maxX && + windowTopLeftPoint.y >= frame.minY && + windowTopLeftPoint.y <= frame.maxY { + screenFrame = frame + break + } + } + + if adjustedHeight > screenFrame.size.height / 2.0 { + adjustedHeight = 0.0 + } + + let windowSize = window?.frame.size ?? NSSize.zero + + // bottom beneath the screen? + if adjustedPoint.y - windowSize.height < screenFrame.minY { + adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height + } + + // top over the screen? + if adjustedPoint.y >= screenFrame.maxY { + adjustedPoint.y = screenFrame.maxY - 1.0 + } + + // right + if adjustedPoint.x + windowSize.width >= screenFrame.maxX { + adjustedPoint.x = screenFrame.maxX - windowSize.width + } + + // left + if adjustedPoint.x < screenFrame.minX { + adjustedPoint.x = screenFrame.minX + } + + window?.setFrameTopLeftPoint(adjustedPoint) + } + +} diff --git a/Source/CandidateUI/VTHorizontalCandidateController.h b/Source/CandidateUI/VTHorizontalCandidateController.h deleted file mode 100644 index cd3967550..000000000 --- a/Source/CandidateUI/VTHorizontalCandidateController.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// VTHorizontalCandidateController.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "VTCandidateController.h" - -@class VTHorizontalCandidateView; - -@interface VTHorizontalCandidateController : VTCandidateController -{ -@protected - VTHorizontalCandidateView *_candidateView; - NSButton *_prevPageButton; - NSButton *_nextPageButton; - NSUInteger _currentPage; -} -@end diff --git a/Source/CandidateUI/VTHorizontalCandidateController.m b/Source/CandidateUI/VTHorizontalCandidateController.m deleted file mode 100644 index 61cf9a1d0..000000000 --- a/Source/CandidateUI/VTHorizontalCandidateController.m +++ /dev/null @@ -1,267 +0,0 @@ -// -// VTHorizontalCandidateController.m -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "VTHorizontalCandidateController.h" -#import "VTHorizontalCandidateView.h" - -@interface VTHorizontalCandidateController (Private) -- (NSUInteger)pageCount; -- (void)layoutCandidateView; -- (void)pageButtonAction:(id)sender; -- (void)candidateViewMouseDidClick:(id)sender; -@end - - -@implementation VTHorizontalCandidateController - -- (void)dealloc -{ - _candidateView = nil; - _prevPageButton = nil; - _nextPageButton = nil; -} - -- (id)init -{ - NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0); - NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask; - - NSWindow *panel = [[NSWindow alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - [panel setLevel:kCGPopUpMenuWindowLevel]; - [panel setHasShadow:YES]; - [panel setOpaque:NO]; - [panel setBackgroundColor:[NSColor clearColor]]; - - self = [self initWithWindow:panel]; - if (self) { - contentRect.origin = NSMakePoint(0.0, 0.0); - _candidateView = [[VTHorizontalCandidateView alloc] initWithFrame:contentRect]; - _candidateView.target = self; - _candidateView.action = @selector(candidateViewMouseDidClick:); - [[panel contentView] addSubview:_candidateView]; - - contentRect.size = NSMakeSize(16.0, 20.0); - _nextPageButton = [[NSButton alloc] initWithFrame:contentRect]; - _prevPageButton = [[NSButton alloc] initWithFrame:contentRect]; - [_nextPageButton setButtonType:NSMomentaryLightButton]; - [_nextPageButton setBezelStyle:NSBezelStyleSmallSquare]; - [_nextPageButton setTitle:@"↓"]; - [_nextPageButton setTarget:self]; - [_nextPageButton setAction:@selector(pageButtonAction:)]; - [_nextPageButton setWantsLayer: YES]; - [_nextPageButton.layer setCornerRadius: 3]; - [_nextPageButton.layer setBorderColor: [NSColor clearColor].CGColor]; - [_nextPageButton.layer setBorderWidth: 3]; - [_nextPageButton.layer setBackgroundColor: [NSColor windowBackgroundColor].CGColor]; - - [_prevPageButton setButtonType:NSMomentaryLightButton]; - [_prevPageButton setBezelStyle:NSBezelStyleSmallSquare]; - [_prevPageButton setTitle:@"↑"]; - [_prevPageButton setTarget:self]; - [_prevPageButton setAction:@selector(pageButtonAction:)]; - [_prevPageButton setWantsLayer: YES]; - [_prevPageButton.layer setCornerRadius: 3]; - [_prevPageButton.layer setBorderColor: [NSColor clearColor].CGColor]; - [_prevPageButton.layer setBorderWidth: 3]; - [_prevPageButton.layer setBackgroundColor: [NSColor windowBackgroundColor].CGColor]; - - [[panel contentView] addSubview:_nextPageButton]; - [[panel contentView] addSubview:_prevPageButton]; - } - - return self; -} - -- (void)reloadData -{ - _candidateView.highlightedIndex = 0; - _currentPage = 0; - [self layoutCandidateView]; -} - -- (BOOL)showNextPage -{ - if (_currentPage + 1 >= [self pageCount]) { - return NO; - } - - _currentPage++; - _candidateView.highlightedIndex = 0; - [self layoutCandidateView]; - return YES; -} - -- (BOOL)showPreviousPage -{ - if (_currentPage == 0) { - return NO; - } - - _currentPage--; - _candidateView.highlightedIndex = 0; - [self layoutCandidateView]; - return YES; -} - -- (BOOL)highlightNextCandidate -{ - NSUInteger currentIndex = self.selectedCandidateIndex; - if (currentIndex + 1 >= [_delegate candidateCountForController:self]) { - return NO; - } - - self.selectedCandidateIndex = currentIndex + 1; - return YES; -} - -- (BOOL)highlightPreviousCandidate -{ - NSUInteger currentIndex = self.selectedCandidateIndex; - if (currentIndex == 0) { - return NO; - } - - self.selectedCandidateIndex = currentIndex - 1; - return YES; -} - -- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index -{ - NSUInteger result = _currentPage * [_keyLabels count] + index; - return result < [_delegate candidateCountForController:self] ? result : NSUIntegerMax; -} - - -- (NSUInteger)selectedCandidateIndex -{ - return _currentPage * [_keyLabels count] + _candidateView.highlightedIndex; -} - -- (void)setSelectedCandidateIndex:(NSUInteger)newIndex -{ - NSUInteger keyLabelCount = [_keyLabels count]; - if (newIndex < [_delegate candidateCountForController:self]) { - _currentPage = newIndex / keyLabelCount; - _candidateView.highlightedIndex = newIndex % keyLabelCount; - [self layoutCandidateView]; - } -} -@end - - -@implementation VTHorizontalCandidateController (Private) -- (NSUInteger)pageCount -{ - NSUInteger totalCount = [_delegate candidateCountForController:self]; - NSUInteger keyLabelCount = [_keyLabels count]; - return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0); -} - -- (void)layoutCandidateView -{ - [_candidateView setKeyLabelFont:_keyLabelFont candidateFont:_candidateFont]; - - NSMutableArray *candidates = [NSMutableArray array]; - NSUInteger count = [_delegate candidateCountForController:self]; - NSUInteger keyLabelCount = [_keyLabels count]; - for (NSUInteger index = _currentPage * keyLabelCount, j = 0; index < count && j < keyLabelCount; index++, j++) { - [candidates addObject:[_delegate candidateController:self candidateAtIndex:index]]; - } - - [_candidateView setKeyLabels:_keyLabels displayedCandidates:candidates]; - NSSize newSize = _candidateView.sizeForView; - - NSRect frameRect = [_candidateView frame]; - frameRect.size = newSize; - [_candidateView setFrame:frameRect]; - - if ([self pageCount] > 1) { - NSRect buttonRect = [_nextPageButton frame]; - CGFloat spacing = 0.0; - - if (newSize.height < 40.0) { - buttonRect.size.height = floor(newSize.height / 2); - } - else { - buttonRect.size.height = 20.0; - } - - if (newSize.height >= 60.0) { - spacing = ceil(newSize.height * 0.1); - } - - CGFloat buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0; - buttonRect.origin = NSMakePoint(newSize.width + 8.0, buttonOriginY); - [_nextPageButton setFrame:buttonRect]; - - buttonRect.origin = NSMakePoint(newSize.width + 8.0, buttonOriginY + buttonRect.size.height + spacing); - [_prevPageButton setFrame:buttonRect]; - - [_nextPageButton setEnabled:(_currentPage + 1 < [self pageCount])]; - [_prevPageButton setEnabled:(_currentPage != 0)]; - - newSize.width += 52.0; - - [_nextPageButton setHidden:NO]; - [_prevPageButton setHidden:NO]; - } - else { - [_nextPageButton setHidden:YES]; - [_prevPageButton setHidden:YES]; - } - - frameRect = [[self window] frame]; - NSPoint topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height); - - frameRect.size = newSize; - frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height); - - [[self window] setFrame:frameRect display:NO]; - [_candidateView setNeedsDisplay:YES]; - -} - -- (void)pageButtonAction:(id)sender -{ - if (sender == _nextPageButton) { - [self showNextPage]; - } - else if (sender == _prevPageButton) { - [self showPreviousPage]; - } -} - -- (void)candidateViewMouseDidClick:(id)sender -{ - [_delegate candidateController:self didSelectCandidateAtIndex:self.selectedCandidateIndex]; -} -@end diff --git a/Source/CandidateUI/VTHorizontalCandidateView.h b/Source/CandidateUI/VTHorizontalCandidateView.h deleted file mode 100644 index f0d35fa85..000000000 --- a/Source/CandidateUI/VTHorizontalCandidateView.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// VTHorizontalCandidateView.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import - -@interface VTHorizontalCandidateView : NSView -{ -@protected - NSArray *_keyLabels; - NSArray *_displayedCandidates; - CGFloat _keyLabelHeight; - CGFloat _candidateTextHeight; - CGFloat _cellPadding; - NSDictionary *_keyLabelAttrDict; - NSDictionary *_candidateAttrDict; - NSArray *_elementWidths; - NSUInteger _highlightedIndex; - NSUInteger _trackingHighlightedIndex; - SEL _action; - __weak id _target; -} - -- (void)setKeyLabels:(NSArray *)labels displayedCandidates:(NSArray *)candidates; -- (void)setKeyLabelFont:(NSFont *)labelFont candidateFont:(NSFont *)candidateFont; - -@property (readonly, nonatomic) NSSize sizeForView; -@property (assign, nonatomic) NSUInteger highlightedIndex; -@property (assign, nonatomic) SEL action; -@property (weak, nonatomic) id target; -@end diff --git a/Source/CandidateUI/VTHorizontalCandidateView.m b/Source/CandidateUI/VTHorizontalCandidateView.m deleted file mode 100644 index a26752d16..000000000 --- a/Source/CandidateUI/VTHorizontalCandidateView.m +++ /dev/null @@ -1,239 +0,0 @@ -// -// VTHorizontalCandidateView.m -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "VTHorizontalCandidateView.h" - -// use these instead of MIN/MAX macro to keep compilers happy with pedantic warnings on -NS_INLINE CGFloat min(CGFloat a, CGFloat b) { return a < b ? a : b; } -NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } - -@implementation VTHorizontalCandidateView - -@synthesize highlightedIndex = _highlightedIndex; -@synthesize action = _action; -@synthesize target = _target; - -static NSColor *colorFromRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - return [NSColor colorWithDeviceRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:(a/255.0f)]; -} - -- (void)dealloc -{ - _keyLabels = nil; - _displayedCandidates = nil; - _keyLabelAttrDict = nil; - _candidateAttrDict = nil; - _elementWidths = nil; -} - -- (void)setKeyLabels:(NSArray *)labels displayedCandidates:(NSArray *)candidates -{ - NSUInteger count = min([labels count], [candidates count]); - _keyLabels = [labels subarrayWithRange:NSMakeRange(0, count)]; - _displayedCandidates = [candidates subarrayWithRange:NSMakeRange(0, count)]; - - NSMutableArray *newWidths = [NSMutableArray array]; - - NSSize baseSize = NSMakeSize(10240.0, 10240.0); - for (NSUInteger index = 0; index < count; index++) { - NSRect labelRect = [[_keyLabels objectAtIndex:index] boundingRectWithSize:baseSize options:NSStringDrawingUsesLineFragmentOrigin attributes:_keyLabelAttrDict]; - - NSRect candidateRect = [[_displayedCandidates objectAtIndex:index] boundingRectWithSize:baseSize options:NSStringDrawingUsesLineFragmentOrigin attributes:_candidateAttrDict]; - - CGFloat width = max(labelRect.size.width, candidateRect.size.width) + _cellPadding; - [newWidths addObject:[NSNumber numberWithDouble:width]]; - } - - _elementWidths = newWidths; -} - -- (void)setKeyLabelFont:(NSFont *)labelFont candidateFont:(NSFont *)candidateFont -{ - NSColor *clrCandidateText = colorFromRGBA(233,233,233,255); - NSColor *clrCandidateTextIndex = colorFromRGBA(233,233,233,213); - NSMutableParagraphStyle *paraStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - [paraStyle setAlignment:NSCenterTextAlignment]; - - _keyLabelAttrDict = [NSDictionary dictionaryWithObjectsAndKeys: - labelFont, NSFontAttributeName, - paraStyle, NSParagraphStyleAttributeName, - clrCandidateTextIndex, NSForegroundColorAttributeName, - nil]; - _candidateAttrDict = [NSDictionary dictionaryWithObjectsAndKeys: - candidateFont, NSFontAttributeName, - paraStyle, NSParagraphStyleAttributeName, - clrCandidateText, NSForegroundColorAttributeName, - nil]; - - CGFloat labelFontSize = [labelFont pointSize]; - CGFloat candidateFontSize = [candidateFont pointSize]; - CGFloat biggestSize = max(labelFontSize, candidateFontSize); - - _keyLabelHeight = ceil(labelFontSize * 1.20); - _candidateTextHeight = ceil(candidateFontSize * 1.20); - _cellPadding = ceil(biggestSize / 2.0); -} - - -- (NSSize)sizeForView -{ - NSSize result = NSMakeSize(0.0, 0.0); - if ([_elementWidths count]) { - for (NSNumber *w in _elementWidths) { - result.width += [w doubleValue]; - } - - result.width += [_elementWidths count]; - result.height = _keyLabelHeight + _candidateTextHeight + 1.0; - } - - return result; -} - -- (BOOL)isFlipped -{ - return YES; -} - -- (void)drawRect:(NSRect)dirtyRect -{ - NSColor *clrCandidateSelectedBG = [NSColor alternateSelectedControlColor]; - NSColor *clrCandidateSelectedText = colorFromRGBA(233,233,233,255); - NSColor *clrCandidateWindowBorder = colorFromRGBA(255,255,255,75); - NSColor *clrCandidateWindowBG = colorFromRGBA(28,28,28,255); - NSColor *clrCandidateBG = colorFromRGBA(28,28,28,255); - - [self setWantsLayer: YES]; - [self.layer setBorderColor: [clrCandidateWindowBorder CGColor]]; - [self.layer setBorderWidth: 1]; - [self.layer setCornerRadius: 6]; - - NSRect bounds = [self bounds]; - - [clrCandidateWindowBG setFill]; - [NSBezierPath fillRect:bounds]; - - NSUInteger count = [_elementWidths count]; - CGFloat accuWidth = 0.0; - - for (NSUInteger index = 0; index < count; index++) { - NSDictionary *activeCandidateAttr = _candidateAttrDict; - CGFloat currentWidth = [[_elementWidths objectAtIndex:index] doubleValue]; - NSRect labelRect = NSMakeRect(accuWidth, 0.0, currentWidth + 1.0, _keyLabelHeight + 1.0); - NSRect candidateRect = NSMakeRect(accuWidth, _keyLabelHeight + 1.0, currentWidth + 1.0, _candidateTextHeight); - - if (index == _highlightedIndex) { - [clrCandidateSelectedBG setFill]; - } - else { - [clrCandidateBG setFill]; - } - - [NSBezierPath fillRect:labelRect]; - [[_keyLabels objectAtIndex:index] drawInRect:labelRect withAttributes:_keyLabelAttrDict]; - - if (index == _highlightedIndex) { - [clrCandidateSelectedBG setFill]; - activeCandidateAttr = [_candidateAttrDict mutableCopy]; - [(NSMutableDictionary *)activeCandidateAttr setObject:clrCandidateSelectedText forKey:NSForegroundColorAttributeName]; - } - else { - [clrCandidateBG setFill]; - } - - [NSBezierPath fillRect:candidateRect]; - [[_displayedCandidates objectAtIndex:index] drawInRect:candidateRect withAttributes:activeCandidateAttr]; - - accuWidth += currentWidth + 1.0; - } -} - -- (NSUInteger)findHitIndex:(NSEvent *)theEvent -{ - NSUInteger result = NSUIntegerMax; - - NSPoint location = [self convertPoint:[theEvent locationInWindow] toView:nil]; - if (!NSPointInRect(location, [self bounds])) { - return result; - } - - NSUInteger count = [_elementWidths count]; - CGFloat accuWidth = 0.0; - for (NSUInteger index = 0; index < count; index++) { - CGFloat currentWidth = [[_elementWidths objectAtIndex:index] doubleValue]; - - if (location.x >= accuWidth && location.x <= accuWidth + currentWidth) { - result = index; - break; - } - accuWidth += currentWidth; - } - - return result; -} - -- (void)mouseDown:(NSEvent *)theEvent -{ - NSUInteger newIndex = [self findHitIndex:theEvent]; - _trackingHighlightedIndex = _highlightedIndex; - - if (newIndex != NSUIntegerMax) { - _highlightedIndex = newIndex; - [self setNeedsDisplay:YES]; - } -} - -- (void)mouseUp:(NSEvent *)theEvent -{ - NSUInteger newIndex = [self findHitIndex:theEvent]; - BOOL triggerAction = NO; - - if (newIndex == _highlightedIndex) { - triggerAction = YES; - } - else { - _highlightedIndex = _trackingHighlightedIndex; - } - - _trackingHighlightedIndex = 0; - [self setNeedsDisplay:YES]; - -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Warc-performSelector-leaks" - if (triggerAction && _target && _action) { - [_target performSelector:_action withObject:self]; - } -# pragma clang diagnostic pop -} - -@end diff --git a/Source/CandidateUI/VTVerticalCandidateController.h b/Source/CandidateUI/VTVerticalCandidateController.h deleted file mode 100644 index 04bdb596e..000000000 --- a/Source/CandidateUI/VTVerticalCandidateController.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// VTVerticalCandidateController.h -// -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "VTCandidateController.h" - -@class VTVerticalKeyLabelStripView; - -@interface VTVerticalCandidateController : VTCandidateController -{ -@protected - VTVerticalKeyLabelStripView *_keyLabelStripView; - NSScrollView *_scrollView; - NSTableView *_tableView; - NSMutableParagraphStyle *_candidateTextParagraphStyle; - CGFloat _maxCandidateAttrStringWidth; -} -@end diff --git a/Source/CandidateUI/VTVerticalCandidateController.m b/Source/CandidateUI/VTVerticalCandidateController.m deleted file mode 100644 index ba4c32399..000000000 --- a/Source/CandidateUI/VTVerticalCandidateController.m +++ /dev/null @@ -1,469 +0,0 @@ -// -// VTVerticalCandidateController.m -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "VTVerticalCandidateController.h" -#import "VTVerticalKeyLabelStripView.h" -#import "VTVerticalCandidateTableView.h" - -// use these instead of MIN/MAX macro to keep compilers happy with pedantic warnings on -NS_INLINE CGFloat min(CGFloat a, CGFloat b) { return a < b ? a : b; } -NS_INLINE CGFloat max(CGFloat a, CGFloat b) { return a > b ? a : b; } - -static const CGFloat kCandidateTextPadding = 24.0; -static const CGFloat kCandidateTextLeftMargin = 8.0; - -static NSColor *colorFromRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - return [NSColor colorWithDeviceRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:(a/255.0f)]; -} - -#if defined(__MAC_11_0) -static const CGFloat kCandidateTextPaddingWithMandatedTableViewPadding = 18.0; -static const CGFloat kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0; -#endif - -@interface VTVerticalCandidateController (Private) -- (void)rowDoubleClicked:(id)sender; -- (BOOL)scrollPageByOne:(BOOL)forward; -- (BOOL)moveSelectionByOne:(BOOL)forward; -- (void)layoutCandidateView; -@end - -@implementation VTVerticalCandidateController -{ - // Total padding added to the left and the right of the table view cell text. - CGFloat _candidateTextPadding; - - // The indent of the table view cell text from the left. - CGFloat _candidateTextLeftMargin; -} - - -- (id)init -{ - // NSColor *clrCandidateSelectedBG = [NSColor systemBlueColor]; - NSColor *clrCandidateSelectedText = [[NSColor whiteColor] colorWithAlphaComponent: 0.8]; - NSColor *clrCandidateWindowBorder = colorFromRGBA(255,255,255,75); - NSColor *clrCandidateWindowBG = colorFromRGBA(28,28,28,255); - // NSColor *clrCandidateBG = colorFromRGBA(28,28,28,255); - - NSRect contentRect = NSMakeRect(128.0, 128.0, 0.0, 0.0); - NSUInteger styleMask = NSBorderlessWindowMask | NSNonactivatingPanelMask; - NSView *panelView = [[NSView alloc] initWithFrame:contentRect]; - NSWindow *panel = [[NSWindow alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - [panel setLevel:kCGPopUpMenuWindowLevel]; - [panel setContentView: panelView]; - [panel setHasShadow:YES]; - [panel setOpaque:NO]; - [panel setBackgroundColor: [NSColor clearColor]]; - [panel setOpaque:false]; - [panelView setWantsLayer: YES]; - [panelView.layer setBorderColor: [clrCandidateWindowBorder CGColor]]; - [panelView.layer setBorderWidth: 1]; - [panelView.layer setCornerRadius: 6]; - [panelView.layer setBackgroundColor: [clrCandidateWindowBG CGColor]]; - - self = [self initWithWindow:panel]; - if (self) { - contentRect.origin = NSMakePoint(0.0, 0.0); - - NSRect stripRect = contentRect; - stripRect.size.width = 10.0; - _keyLabelStripView = [[VTVerticalKeyLabelStripView alloc] initWithFrame:stripRect]; - [_keyLabelStripView setWantsLayer: YES]; - [_keyLabelStripView.layer setBorderWidth: 0]; - - [[panel contentView] addSubview:_keyLabelStripView]; - - NSRect scrollViewRect = contentRect; - scrollViewRect.origin.x = stripRect.size.width; - scrollViewRect.size.width -= stripRect.size.width; - - _scrollView = [[NSScrollView alloc] initWithFrame:scrollViewRect]; - [_scrollView setAutohidesScrollers: YES]; - [_scrollView setWantsLayer: YES]; - [_scrollView.layer setBorderWidth: 0]; - [_scrollView setDrawsBackground:NO]; - - // >=10.7 only, elastic scroll causes some drawing issues with visible scroller, so we disable it - if ([_scrollView respondsToSelector:@selector(setVerticalScrollElasticity:)]) { - [_scrollView setVerticalScrollElasticity:NSScrollElasticityNone]; - } - - _tableView = [[VTVerticalCandidateTableView alloc] initWithFrame:contentRect]; - [_tableView setDataSource:self]; - [_tableView setDelegate:self]; - - NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"candidate"]; - [column setDataCell:[[NSTextFieldCell alloc] init]]; - [column setEditable:NO]; - [column.dataCell setTextColor: clrCandidateSelectedText]; - // [column.dataCell setSelectionColor: clrCandidateSelectedBG]; - - _candidateTextPadding = kCandidateTextPadding; - _candidateTextLeftMargin = kCandidateTextLeftMargin; - - [_tableView addTableColumn:column]; - [_tableView setIntercellSpacing:NSMakeSize(0.0, 1.0)]; - [_tableView setHeaderView:nil]; - [_tableView setAllowsMultipleSelection:NO]; - [_tableView setAllowsEmptySelection:YES]; - [_tableView setDoubleAction:@selector(rowDoubleClicked:)]; - [_tableView setTarget:self]; - [_tableView setBackgroundColor:[NSColor clearColor]]; - [_tableView setGridColor:[NSColor clearColor]]; - - #if defined(__MAC_11_0) - if (@available(macOS 11.0, *)) { - [_tableView setStyle:NSTableViewStyleFullWidth]; - _candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding; - _candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding; - } - #endif - - [_scrollView setDocumentView:_tableView]; - [[panel contentView] addSubview:_scrollView]; - - _candidateTextParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - [_candidateTextParagraphStyle setFirstLineHeadIndent:_candidateTextLeftMargin]; - [_candidateTextParagraphStyle setLineBreakMode:NSLineBreakByClipping]; - } - - return self; -} - -- (void)reloadData -{ - _maxCandidateAttrStringWidth = ceil([_candidateFont pointSize] * 2.0 + _candidateTextPadding); - - [_tableView reloadData]; - [self layoutCandidateView]; - - if ([_delegate candidateCountForController:self]) { - self.selectedCandidateIndex = 0; - } -} - -- (BOOL)showNextPage -{ - return [self scrollPageByOne:YES]; -} - -- (BOOL)showPreviousPage -{ - return [self scrollPageByOne:NO]; -} - -- (BOOL)highlightNextCandidate -{ - return [self moveSelectionByOne:YES]; -} - -- (BOOL)highlightPreviousCandidate -{ - return [self moveSelectionByOne:NO]; -} - -- (NSUInteger)candidateIndexAtKeyLabelIndex:(NSUInteger)index -{ - NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin]; - if (firstVisibleRow != -1) { - NSUInteger result = firstVisibleRow + index; - if (result < [_delegate candidateCountForController:self]) { - return result; - } - } - - return NSUIntegerMax; -} - -- (NSUInteger)selectedCandidateIndex -{ - NSInteger selectedRow = [_tableView selectedRow]; - return (selectedRow == -1) ? NSUIntegerMax : selectedRow; -} - -- (void)setSelectedCandidateIndex:(NSUInteger)aNewIndex -{ - NSUInteger newIndex = aNewIndex; - - NSInteger selectedRow = [_tableView selectedRow]; - - NSUInteger labelCount = [_keyLabels count]; - NSUInteger itemCount = [_delegate candidateCountForController:self]; - - if (newIndex == NSUIntegerMax) { - if (itemCount == 0) { - [_tableView deselectAll:self]; - return; - } - newIndex = 0; - } - - NSUInteger lastVisibleRow = newIndex; - if (selectedRow != -1 && itemCount > 0 && itemCount > labelCount) { - if (newIndex > selectedRow && (newIndex - selectedRow) > 1) { - lastVisibleRow = min(newIndex + labelCount - 1, itemCount - 1); - } - - // no need to handle the backward case: (newIndex < selectedRow && selectedRow - newIndex > 1) - } - - if (itemCount > labelCount) { - [_tableView scrollRowToVisible:lastVisibleRow]; - } - - [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex] byExtendingSelection:NO]; -} - - -@end - - -@implementation VTVerticalCandidateController (Private) -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView -{ - return [_delegate candidateCountForController:self]; -} - -- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row -{ - NSString *candidate = @""; - - // rendering can occur when the delegate is already gone or data goes stale; in that case we ignore it - - if (row < [_delegate candidateCountForController:self]) { - candidate = [_delegate candidateController:self candidateAtIndex:row]; - } - - NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:candidate attributes:[NSDictionary dictionaryWithObjectsAndKeys:_candidateFont, NSFontAttributeName, _candidateTextParagraphStyle, NSParagraphStyleAttributeName, nil]]; - - // we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead - - // expand the window width if text overflows - NSRect boundingRect = [attrString boundingRectWithSize:NSMakeSize(10240.0, 10240.0) options:NSStringDrawingUsesLineFragmentOrigin]; - CGFloat textWidth = boundingRect.size.width + _candidateTextPadding; - if (textWidth > _maxCandidateAttrStringWidth) { - _maxCandidateAttrStringWidth = textWidth; - [self layoutCandidateView]; - } - - // keep track of the highlighted index in the key label strip - NSUInteger count = [_keyLabels count]; - NSInteger selectedRow = [_tableView selectedRow]; - if (selectedRow != -1) { - // cast this into signed integer to make our life easier - NSInteger newHilightIndex; - - if (_keyLabelStripView.highlightedIndex != -1 && (row >= selectedRow + count || (selectedRow > count && row <= selectedRow - count))) { - newHilightIndex = -1; - } - else { - NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin]; - - newHilightIndex = selectedRow - firstVisibleRow; - if (newHilightIndex < -1) { - newHilightIndex = -1; - } - } - - if (newHilightIndex != _keyLabelStripView.highlightedIndex && newHilightIndex >= 0) { - _keyLabelStripView.highlightedIndex = newHilightIndex; - [_keyLabelStripView setNeedsDisplay:YES]; - } - } - - return attrString; -} - -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification -{ - NSInteger selectedRow = [_tableView selectedRow]; - if (selectedRow != -1) { - // keep track of the highlighted index in the key label strip - NSInteger firstVisibleRow = [_tableView rowAtPoint:[_scrollView documentVisibleRect].origin]; - _keyLabelStripView.highlightedIndex = selectedRow - firstVisibleRow; - [_keyLabelStripView setNeedsDisplay:YES]; - - // fix a subtle OS X "bug" that, since we force the scroller to appear, - // scrolling sometimes shows a temporarily "broken" scroll bar - // (but quickly disappears) - if ([_scrollView hasVerticalScroller]) { - [[_scrollView verticalScroller] setNeedsDisplay]; - } - } -} - -- (void)rowDoubleClicked:(id)sender -{ - NSInteger clickedRow = [_tableView clickedRow]; - if (clickedRow != -1) { - [_delegate candidateController:self didSelectCandidateAtIndex:clickedRow]; - } -} - -- (BOOL)scrollPageByOne:(BOOL)forward -{ - NSUInteger labelCount = [_keyLabels count]; - NSUInteger itemCount = [_delegate candidateCountForController:self]; - - if (0 == itemCount) { - return NO; - } - - if (itemCount <= labelCount) { - return NO; - } - - NSUInteger newIndex = self.selectedCandidateIndex; - - if (forward) { - if (newIndex == itemCount - 1) { - return NO; - } - - newIndex = min(newIndex + labelCount, itemCount - 1); - } - else { - if (newIndex == 0) { - return NO; - } - - if (newIndex < labelCount) { - newIndex = 0; - } - else { - newIndex -= labelCount; - } - } - - self.selectedCandidateIndex = newIndex; - return YES; -} - -- (BOOL)moveSelectionByOne:(BOOL)forward -{ - NSUInteger itemCount = [_delegate candidateCountForController:self]; - - if (0 == itemCount) { - return NO; - } - - NSUInteger newIndex = self.selectedCandidateIndex; - - if (forward) { - if (newIndex == itemCount - 1) { - return NO; - } - - newIndex++; - } - else { - if (0 == newIndex) { - return NO; - } - - newIndex--; - } - - self.selectedCandidateIndex = newIndex; - return YES; -} - -- (void)layoutCandidateView -{ - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(doLayoutCanaditeView) object:nil]; - [self performSelector:@selector(doLayoutCanaditeView) withObject:nil afterDelay:0.0]; -} - -- (void)doLayoutCanaditeView -{ - NSUInteger count = [_delegate candidateCountForController:self]; - if (!count) { - return; - } - - CGFloat candidateFontSize = ceil([_candidateFont pointSize]); - CGFloat keyLabelFontSize = ceil([_keyLabelFont pointSize]); - CGFloat fontSize = max(candidateFontSize, keyLabelFontSize); - - NSControlSize controlSize = (fontSize > 36.0) ? NSRegularControlSize : NSSmallControlSize; - - NSUInteger keyLabelCount = [_keyLabels count]; - CGFloat scrollerWidth = 0.0; - if (count <= keyLabelCount) { - keyLabelCount = count; - [_scrollView setHasVerticalScroller:NO]; - } - else { - [_scrollView setHasVerticalScroller:YES]; - - NSScroller *verticalScroller = [_scrollView verticalScroller]; - [verticalScroller setControlSize:controlSize]; - [verticalScroller setScrollerStyle:NSScrollerStyleOverlay]; - scrollerWidth = [NSScroller scrollerWidthForControlSize:controlSize scrollerStyle:NSScrollerStyleOverlay]; - } - - _keyLabelStripView.keyLabelFont = _keyLabelFont; - _keyLabelStripView.keyLabels = [_keyLabels subarrayWithRange:NSMakeRange(0, keyLabelCount)]; - _keyLabelStripView.labelOffsetY = (keyLabelFontSize >= candidateFontSize) ? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0); - - CGFloat rowHeight = ceil(fontSize * 1.25); - [_tableView setRowHeight:rowHeight]; - - CGFloat maxKeyLabelWidth = keyLabelFontSize; - NSDictionary *textAttr = [NSDictionary dictionaryWithObjectsAndKeys: - _keyLabelFont, NSFontAttributeName, - nil]; - NSSize boundingBox = NSMakeSize(1600.0, 1600.0); - for (NSString *label in _keyLabels) { - NSRect rect = [label boundingRectWithSize:boundingBox options:NSStringDrawingUsesLineFragmentOrigin attributes:textAttr]; - maxKeyLabelWidth = max(rect.size.width, maxKeyLabelWidth); - } - - CGFloat rowSpacing = [_tableView intercellSpacing].height; - CGFloat stripWidth = ceil(maxKeyLabelWidth); - CGFloat tableViewStartWidth = ceil(_maxCandidateAttrStringWidth + scrollerWidth);; - CGFloat windowWidth = stripWidth + tableViewStartWidth; - CGFloat windowHeight = keyLabelCount * (rowHeight + rowSpacing); - - NSRect frameRect = [[self window] frame]; - NSPoint topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height); - - frameRect.size = NSMakeSize(windowWidth, windowHeight); - frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height); - - [_keyLabelStripView setFrame:NSMakeRect(0.0, 0.0, stripWidth, windowHeight)]; - [_scrollView setFrame:NSMakeRect(stripWidth, 0.0, tableViewStartWidth, windowHeight)]; - [[self window] setFrame:frameRect display:NO]; -} -@end diff --git a/Source/CandidateUI/VTVerticalCandidateTableView.h b/Source/CandidateUI/VTVerticalCandidateTableView.h deleted file mode 100644 index 47bb37acc..000000000 --- a/Source/CandidateUI/VTVerticalCandidateTableView.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// VTVerticalCandidateTableView.h -// -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import - -@interface VTVerticalCandidateTableView : NSTableView -@end diff --git a/Source/CandidateUI/VTVerticalCandidateTableView.m b/Source/CandidateUI/VTVerticalCandidateTableView.m deleted file mode 100644 index 990f134da..000000000 --- a/Source/CandidateUI/VTVerticalCandidateTableView.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// VTVerticalCandidateTableView.m -// -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "VTVerticalCandidateTableView.h" - -@implementation VTVerticalCandidateTableView -- (NSRect)adjustScroll:(NSRect)newVisible -{ - NSRect scrollRect = newVisible; - CGFloat rowHeightPlusSpacing = [self rowHeight] + [self intercellSpacing].height; - scrollRect.origin.y = (NSInteger)(scrollRect.origin.y / rowHeightPlusSpacing) * rowHeightPlusSpacing; - return scrollRect; -} -@end diff --git a/Source/CandidateUI/VTVerticalKeyLabelStripView.h b/Source/CandidateUI/VTVerticalKeyLabelStripView.h deleted file mode 100644 index 8439ba1a3..000000000 --- a/Source/CandidateUI/VTVerticalKeyLabelStripView.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// VTVerticalKeyLabelStripView.m -// -// Copyright (c) 2012 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import - -@interface VTVerticalKeyLabelStripView : NSView -{ -@protected - NSFont *_keyLabelFont; - CGFloat _labelOffsetY; - NSArray *_keyLabels; - NSInteger _highlightedIndex; -} - -@property (retain, nonatomic) NSFont *keyLabelFont; -@property (assign, nonatomic) CGFloat labelOffsetY; -@property (retain, nonatomic) NSArray *keyLabels; -@property (assign, nonatomic) NSInteger highlightedIndex; -@end diff --git a/Source/CandidateUI/VTVerticalKeyLabelStripView.m b/Source/CandidateUI/VTVerticalKeyLabelStripView.m deleted file mode 100644 index 7469dc546..000000000 --- a/Source/CandidateUI/VTVerticalKeyLabelStripView.m +++ /dev/null @@ -1,114 +0,0 @@ -// -// VTVerticalKeyLabelStripView.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "VTVerticalKeyLabelStripView.h" - -@implementation VTVerticalKeyLabelStripView -@synthesize keyLabelFont = _keyLabelFont; -@synthesize labelOffsetY = _labelOffsetY; -@synthesize keyLabels = _keyLabels; -@synthesize highlightedIndex = _highlightedIndex; - -static NSColor *colorFromRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - return [NSColor colorWithDeviceRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:(a/255.0f)]; -} - -- (void)dealloc -{ - _keyLabelFont = nil; - _keyLabels = nil; -} - -- (id)initWithFrame:(NSRect)frameRect -{ - self = [super initWithFrame:frameRect]; - if (self) { - _keyLabelFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; - } - - return self; -} - -- (BOOL)isFlipped -{ - return YES; -} - -- (void)drawRect:(NSRect)dirtyRect -{ - NSRect bounds = [self bounds]; - [[NSColor clearColor] setFill]; - [NSBezierPath fillRect:bounds]; - - NSUInteger count = [_keyLabels count]; - if (!count) { - return; - } - - CGFloat cellHeight = bounds.size.height / count; - NSColor *clrCandidateBG = colorFromRGBA(28,28,28,255); - NSColor *clrCandidateTextIndex = colorFromRGBA(233,233,233,213); - NSColor *clrCandidateSelectedBG = [NSColor alternateSelectedControlColor]; - - NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - [style setAlignment:NSCenterTextAlignment]; - - NSDictionary *textAttr = [NSDictionary dictionaryWithObjectsAndKeys: - _keyLabelFont, NSFontAttributeName, - clrCandidateTextIndex, NSForegroundColorAttributeName, - style, NSParagraphStyleAttributeName, - nil]; - - for (NSUInteger index = 0; index < count; index++) { - NSRect textRect = NSMakeRect(0.0, index * cellHeight + _labelOffsetY, bounds.size.width, cellHeight - _labelOffsetY); - NSRect cellRect = NSMakeRect(0.0, index * cellHeight, bounds.size.width, cellHeight); - - // fill in the last cell - if (index + 1 >= count) { - cellRect.size.height += 1.0; - } - - if (index == _highlightedIndex) { - [clrCandidateSelectedBG setFill]; - } - else { - [clrCandidateBG setFill]; - } - - [NSBezierPath fillRect:cellRect]; - - NSString *text = [_keyLabels objectAtIndex:index]; - [text drawInRect:textRect withAttributes:textAttr]; - } -} -@end diff --git a/Source/CandidateUI/VerticalCandidateController.swift b/Source/CandidateUI/VerticalCandidateController.swift new file mode 100644 index 000000000..1333831fb --- /dev/null +++ b/Source/CandidateUI/VerticalCandidateController.swift @@ -0,0 +1,455 @@ +// +// VerticalCandidateController.swift +// +// Copyright (c) 2011 The McBopomofo Project. +// +// Contributors: +// Mengjuei Hsieh (@mjhsieh) +// Weizhong Yang (@zonble) +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +fileprivate class VerticalKeyLabelStripView: NSView { + var keyLabelFont: NSFont = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize) + var labelOffsetY: CGFloat = 0 + var keyLabels: [String] = [] + var highlightedIndex: UInt = UInt.max + + override var isFlipped: Bool { + true + } + + override func draw(_ dirtyRect: NSRect) { + let bounds = self.bounds + NSColor.white.setFill() + NSBezierPath.fill(bounds) + + let count = UInt(keyLabels.count) + if count == 0 { + return + } + let cellHeight: CGFloat = bounds.size.height / CGFloat(count) + let black = NSColor.black + let darkGray = NSColor(deviceWhite: 0.7, alpha: 1.0) + let lightGray = NSColor(deviceWhite: 0.8, alpha: 1.0) + + let paraStyle = NSMutableParagraphStyle() + paraStyle.setParagraphStyle(NSParagraphStyle.default) + paraStyle.alignment = .center + + let textAttr: [NSAttributedString.Key: AnyObject] = [ + .font: keyLabelFont, + .foregroundColor: black, + .paragraphStyle: paraStyle] + for index in 0..= count { + cellRect.size.height += 1.0 + } + + (index == highlightedIndex ? darkGray : lightGray).setFill() + NSBezierPath.fill(cellRect) + let text = keyLabels[Int(index)] + (text as NSString).draw(in: textRect, withAttributes: textAttr) + } + } +} + +fileprivate class VerticalCandidateTableView: NSTableView { + override func adjustScroll(_ newVisible: NSRect) -> NSRect { + var scrollRect = newVisible + let rowHeightPlusSpacing = rowHeight + intercellSpacing.height + scrollRect.origin.y = (scrollRect.origin.y / rowHeightPlusSpacing) * rowHeightPlusSpacing + return scrollRect + } +} + +private let kCandidateTextPadding = 24.0 +private let kCandidateTextLeftMargin = 8.0 +private let kCandidateTextPaddingWithMandatedTableViewPadding = 18.0 +private let kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0 + + +@objc (VTVerticalCandidateController) +public class VerticalCandidateController: CandidateController { + private var keyLabelStripView: VerticalKeyLabelStripView + private var scrollView: NSScrollView + private var tableView: NSTableView + private var candidateTextParagraphStyle: NSMutableParagraphStyle + private var candidateTextPadding: CGFloat = kCandidateTextPadding + private var candidateTextLeftMargin: CGFloat = kCandidateTextLeftMargin + private var maxCandidateAttrStringWidth: CGFloat = 0 + + public init() { + var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) + let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] + let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.hasShadow = true + + contentRect.origin = NSPoint.zero + var stripRect = contentRect + stripRect.size.width = 10.0 + keyLabelStripView = VerticalKeyLabelStripView(frame: stripRect) + panel.contentView?.addSubview(keyLabelStripView) + + var scrollViewRect = contentRect + scrollViewRect.origin.x = stripRect.size.width + scrollViewRect.size.width -= stripRect.size.width + scrollView = NSScrollView(frame: scrollViewRect) + scrollView.verticalScrollElasticity = .none + + tableView = NSTableView(frame: contentRect) + let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "candidate")) + column.dataCell = NSTextFieldCell() + column.isEditable = false + + candidateTextPadding = kCandidateTextPadding + candidateTextLeftMargin = kCandidateTextLeftMargin + + tableView.addTableColumn(column) + tableView.intercellSpacing = NSSize(width: 0.0, height: 1.0) + tableView.headerView = nil + tableView.allowsMultipleSelection = false + tableView.allowsEmptySelection = false + + if #available(macOS 10.16, *) { + tableView.style = .fullWidth + candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding + candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding + } + + scrollView.documentView = tableView + panel.contentView?.addSubview(scrollView) + + let paraStyle = NSMutableParagraphStyle() + paraStyle.setParagraphStyle(NSParagraphStyle.default) + paraStyle.firstLineHeadIndent = candidateTextLeftMargin + paraStyle.lineBreakMode = .byClipping + + candidateTextParagraphStyle = paraStyle + + super.init(window: panel) + tableView.dataSource = self + tableView.delegate = self + tableView.doubleAction = #selector(rowDoubleClicked(_:)) + tableView.target = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func reloadData() { + maxCandidateAttrStringWidth = ceil(candidateFont.pointSize * 2.0 + candidateTextPadding) + tableView.reloadData() + layoutCandidateView() + if delegate?.candidateCountForController(self) ?? 0 > 0 { + selectedCandidateIndex = 0 + } + } + + public override func showNextPage() -> Bool { + scrollPageByOne(true) + } + + public override func showPreviousPage() -> Bool { + scrollPageByOne(false) + } + + public override func highlightNextCandidate() -> Bool { + moveSelectionByOne(true) + } + + public override func highlightPreviousCandidate() -> Bool { + moveSelectionByOne(false) + } + + public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { + guard let delegate = delegate else { + return UInt.max + } + + let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) + if firstVisibleRow != -1 { + let result = UInt(firstVisibleRow) + index + if result < delegate.candidateCountForController(self) { + return result + } + } + + return UInt.max + } + + public override var selectedCandidateIndex: UInt { + get { + let selectedRow = tableView.selectedRow + return selectedRow == -1 ? UInt.max : UInt(selectedRow) + + } + set { + guard let delegate = delegate else { + return + } + var newIndex = newValue + let selectedRow = tableView.selectedRow + let labelCount = keyLabels.count + let itemCount = delegate.candidateCountForController(self) + + if newIndex == UInt.max { + if itemCount == 0 { + tableView.deselectAll(self) + return + } + newIndex = 0 + } + + var lastVisibleRow = newValue + + if selectedRow != -1 && itemCount > 0 && itemCount > labelCount { + if newIndex > selectedRow && (Int(newIndex) - selectedRow) > 1 { + lastVisibleRow = min(newIndex + UInt(labelCount) - 1, itemCount - 1) + } + // no need to handle the backward case: (newIndex < selectedRow && selectedRow - newIndex > 1) + } + + if itemCount > labelCount { + tableView.scrollRowToVisible(Int(lastVisibleRow)) + } + tableView.selectRowIndexes(IndexSet(integer: Int(newIndex)), byExtendingSelection: false) + } + } +} + +extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegate { + + public func numberOfRows(in tableView: NSTableView) -> Int { + Int(delegate?.candidateCountForController(self) ?? 0) + } + + public func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { + guard let delegate = delegate else { + return nil + } + var candidate = "" + if row < delegate.candidateCountForController(self) { + candidate = delegate.candidateController(self, candidateAtIndex: UInt(row)) + } + let attrString = NSAttributedString(string: candidate, attributes: [ + .font: candidateFont, + .paragraphStyle: candidateTextParagraphStyle + ]) + + // we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead + + // expand the window width if text overflows + let boundingRect = attrString.boundingRect(with: NSSize(width: 10240.0, height: 10240.0), options: .usesLineFragmentOrigin) + let textWidth = boundingRect.size.width + candidateTextPadding + if textWidth > maxCandidateAttrStringWidth { + maxCandidateAttrStringWidth = textWidth + layoutCandidateView() + } + + // keep track of the highlighted index in the key label strip + let count = UInt(keyLabels.count) + let selectedRow = tableView.selectedRow + + if selectedRow != -1 { + var newHilightIndex = 0 + + if keyLabelStripView.highlightedIndex != -1 && + (row >= selectedRow + Int(count) || (selectedRow > count && row <= selectedRow - Int(count))) { + newHilightIndex = -1 + } else { + let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) + newHilightIndex = selectedRow - firstVisibleRow + if newHilightIndex < -1 { + newHilightIndex = -1 + } + } + + if newHilightIndex != keyLabelStripView.highlightedIndex && newHilightIndex >= 0 { + keyLabelStripView.highlightedIndex = UInt(newHilightIndex) + keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame) + } + + } + return attrString + } + + public func tableViewSelectionDidChange(_ notification: Notification) { + let selectedRow = tableView.selectedRow + if selectedRow != -1 { + // keep track of the highlighted index in the key label strip + let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) + keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow) + keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame) + + // fix a subtle OS X "bug" that, since we force the scroller to appear, + // scrolling sometimes shows a temporarily "broken" scroll bar + // (but quickly disappears) + if scrollView.hasVerticalScroller { + scrollView.verticalScroller?.setNeedsDisplay() + } + } + } + + @objc func rowDoubleClicked(_ sender: Any) { + let clickedRow = tableView.clickedRow + if clickedRow != -1 { + delegate?.candidateController(self, didSelectCandidateAtIndex: UInt(clickedRow)) + } + } + + func scrollPageByOne(_ forward: Bool) -> Bool { + guard let delegate = delegate else { + return false + } + let labelCount = UInt(keyLabels.count) + let itemCount = delegate.candidateCountForController(self) + if 0 == itemCount { + return false + } + if itemCount <= labelCount { + return false + } + + var newIndex = selectedCandidateIndex + if forward { + if newIndex == itemCount - 1 { + return false + } + newIndex = min(newIndex + labelCount, itemCount - 1) + } else { + if newIndex == 0 { + return false + } + + if newIndex < labelCount { + newIndex = 0 + } else { + newIndex -= labelCount + } + } + selectedCandidateIndex = newIndex + return true + } + + private func moveSelectionByOne(_ forward: Bool) -> Bool { + guard let delegate = delegate else { + return false + } + let itemCount = delegate.candidateCountForController(self) + if 0 == itemCount { + return false + } + var newIndex = selectedCandidateIndex + if forward { + if newIndex == itemCount - 1 { + return false + } + newIndex += 1 + } else { + if 0 == newIndex { + return false + } + newIndex -= 1 + } + selectedCandidateIndex = newIndex + return true + } + + private func layoutCandidateView() { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { [self] in + doLayoutCandidateView() + } + } + + private func doLayoutCandidateView() { + guard let delegate = delegate else { + return + } + let count = delegate.candidateCountForController(self) + if 0 == count { + return + } + + let candidateFontSize = ceil(candidateFont.pointSize) + let keyLabelFontSize = ceil(keyLabelFont.pointSize) + let fontSize = max(candidateFontSize, keyLabelFontSize) + + let controlSize: NSControl.ControlSize = fontSize > 36.0 ? .regular : .small + + var keyLabelCount = UInt(keyLabels.count) + var scrollerWidth: CGFloat = 0.0 + if count <= keyLabelCount { + keyLabelCount = count + scrollView.hasVerticalScroller = false + } else { + scrollView.hasVerticalScroller = true + let verticalScroller = scrollView.verticalScroller + verticalScroller?.controlSize = controlSize + verticalScroller?.scrollerStyle = .legacy + scrollerWidth = NSScroller.scrollerWidth(for: controlSize, scrollerStyle: .legacy) + } + + keyLabelStripView.keyLabelFont = keyLabelFont + keyLabelStripView.keyLabels = Array(keyLabels[0..= candidateFontSize) ? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0) + + let rowHeight = ceil(fontSize * 1.25) + tableView.rowHeight = rowHeight + + var maxKeyLabelWidth = keyLabelFontSize + let textAttr: [NSAttributedString.Key: AnyObject] = [.font: keyLabelFont] + let boundingBox = NSSize(width: 1600.0, height: 1600.0) + + for label in keyLabels { + let rect = (label as NSString).boundingRect(with: boundingBox, options: .usesLineFragmentOrigin, attributes: textAttr) + maxKeyLabelWidth = max(rect.size.width, maxKeyLabelWidth) + } + + let rowSpacing = tableView.intercellSpacing.height + let stripWidth = ceil(maxKeyLabelWidth * 1.20) + let tableViewStartWidth = ceil(maxCandidateAttrStringWidth + scrollerWidth) + let windowWidth = stripWidth + 1.0 + tableViewStartWidth + let windowHeight = CGFloat(keyLabelCount) * (rowHeight + rowSpacing) + + var frameRect = self.window?.frame ?? NSRect.zero + let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height) + + frameRect.size = NSMakeSize(windowWidth, windowHeight) + frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) + + keyLabelStripView.frame = NSRect(x: 0.0, y: 0.0, width: stripWidth, height: windowHeight) + scrollView.frame = NSRect(x: stripWidth + 1.0, y: 0.0, width: tableViewStartWidth, height: windowHeight) + self.window?.setFrame(frameRect, display: false) + } +} diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 1e398bdbb..6692bfa71 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -43,8 +43,6 @@ #import "OVStringHelper.h" #import "OVUTF8Helper.h" #import "AppDelegate.h" -#import "VTHorizontalCandidateController.h" -#import "VTVerticalCandidateController.h" #import "OVNonModalAlertWindowController.h" #import "vChewing-Swift.h" diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index ae4bbaef5..6e83dec46 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -12,6 +12,9 @@ 5B1958522788A2BF00FAEB14 /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */; }; 5B58E87F278413E7003EA2AD /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; + 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */; }; + 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; + 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC3FB82278492DE0022E99A /* data-chs.txt */; }; 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; @@ -22,12 +25,6 @@ 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC815FC0D6400ABF4B3 /* main.m */; }; 6A0D4ED415FC0D6400ABF4B3 /* OVInputSourceHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */; }; 6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */; }; - 6A0D4EFE15FC0DA600ABF4B3 /* VTCandidateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */; }; - 6A0D4EFF15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EDC15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m */; }; - 6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EDE15FC0DA600ABF4B3 /* VTHorizontalCandidateView.m */; }; - 6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EE015FC0DA600ABF4B3 /* VTVerticalCandidateController.m */; }; - 6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EE215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m */; }; - 6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EE415FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m */; }; 6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; }; 6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; }; 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; }; @@ -91,6 +88,9 @@ 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D92763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.xib"; sourceTree = ""; }; + 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalCandidateController.swift; sourceTree = ""; }; + 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; }; + 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; }; 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; @@ -113,18 +113,6 @@ 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OVInputSourceHelper.m; sourceTree = ""; }; 6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesWindowController.h; sourceTree = ""; }; 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesWindowController.m; sourceTree = ""; }; - 6A0D4ED915FC0DA600ABF4B3 /* VTCandidateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTCandidateController.h; sourceTree = ""; }; - 6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTCandidateController.m; sourceTree = ""; }; - 6A0D4EDB15FC0DA600ABF4B3 /* VTHorizontalCandidateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTHorizontalCandidateController.h; sourceTree = ""; }; - 6A0D4EDC15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTHorizontalCandidateController.m; sourceTree = ""; }; - 6A0D4EDD15FC0DA600ABF4B3 /* VTHorizontalCandidateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTHorizontalCandidateView.h; sourceTree = ""; }; - 6A0D4EDE15FC0DA600ABF4B3 /* VTHorizontalCandidateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTHorizontalCandidateView.m; sourceTree = ""; }; - 6A0D4EDF15FC0DA600ABF4B3 /* VTVerticalCandidateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTVerticalCandidateController.h; sourceTree = ""; }; - 6A0D4EE015FC0DA600ABF4B3 /* VTVerticalCandidateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTVerticalCandidateController.m; sourceTree = ""; }; - 6A0D4EE115FC0DA600ABF4B3 /* VTVerticalCandidateTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTVerticalCandidateTableView.h; sourceTree = ""; }; - 6A0D4EE215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTVerticalCandidateTableView.m; sourceTree = ""; }; - 6A0D4EE315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VTVerticalKeyLabelStripView.h; sourceTree = ""; }; - 6A0D4EE415FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VTVerticalKeyLabelStripView.m; sourceTree = ""; }; 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Bopomofo.tiff; sourceTree = ""; }; 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "Bopomofo@2x.tiff"; sourceTree = ""; }; 6A0D4EF515FC0DA600ABF4B3 /* vChewing-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "vChewing-Info.plist"; sourceTree = ""; }; @@ -307,18 +295,9 @@ 6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */ = { isa = PBXGroup; children = ( - 6A0D4ED915FC0DA600ABF4B3 /* VTCandidateController.h */, - 6A0D4EDA15FC0DA600ABF4B3 /* VTCandidateController.m */, - 6A0D4EDB15FC0DA600ABF4B3 /* VTHorizontalCandidateController.h */, - 6A0D4EDC15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m */, - 6A0D4EDD15FC0DA600ABF4B3 /* VTHorizontalCandidateView.h */, - 6A0D4EDE15FC0DA600ABF4B3 /* VTHorizontalCandidateView.m */, - 6A0D4EDF15FC0DA600ABF4B3 /* VTVerticalCandidateController.h */, - 6A0D4EE015FC0DA600ABF4B3 /* VTVerticalCandidateController.m */, - 6A0D4EE115FC0DA600ABF4B3 /* VTVerticalCandidateTableView.h */, - 6A0D4EE215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m */, - 6A0D4EE315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.h */, - 6A0D4EE415FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m */, + 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */, + 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */, + 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */, ); path = CandidateUI; sourceTree = ""; @@ -646,18 +625,15 @@ 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, 6A0D4ED415FC0D6400ABF4B3 /* OVInputSourceHelper.m in Sources */, + 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, 6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */, - 6A0D4EFE15FC0DA600ABF4B3 /* VTCandidateController.m in Sources */, - 6A0D4EFF15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m in Sources */, - 6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */, 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */, - 6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */, - 6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */, - 6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, + 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, + 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -- Gitee From c66f2c10db49b55e29fff8c3de3aeec0ac1bc3a4 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 14 Jan 2022 22:49:45 +0800 Subject: [PATCH 035/163] Shiki: Swiftify // Voltaire UI Revamp - Vertical --- .../VerticalCandidateController.swift | 192 ++++++++++-------- 1 file changed, 107 insertions(+), 85 deletions(-) diff --git a/Source/CandidateUI/VerticalCandidateController.swift b/Source/CandidateUI/VerticalCandidateController.swift index 1333831fb..2c495617b 100644 --- a/Source/CandidateUI/VerticalCandidateController.swift +++ b/Source/CandidateUI/VerticalCandidateController.swift @@ -1,11 +1,16 @@ // // VerticalCandidateController.swift // -// Copyright (c) 2011 The McBopomofo Project. +// Voltaire IME Candidate Controller Module +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// Beautified by (c) 2021-2022 The vChewing Project. // // Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) +// Lukhnos Liu (@lukhnos) @ OpenVanilla // Original Developer +// Weizhong Yang (@zonble) @ OpenVanilla // Rewriter to Swift +// Shiki Suen (ShikiSuen) @ vChewing // Beautification (objC) + // // Based on the Syrup Project and the Formosana Library // by Lukhnos Liu (@lukhnos). @@ -39,45 +44,46 @@ fileprivate class VerticalKeyLabelStripView: NSView { var labelOffsetY: CGFloat = 0 var keyLabels: [String] = [] var highlightedIndex: UInt = UInt.max - + override var isFlipped: Bool { true } - + override func draw(_ dirtyRect: NSRect) { let bounds = self.bounds - NSColor.white.setFill() + NSColor.clear.setFill() // Disable white base color, just in case. NSBezierPath.fill(bounds) - + let count = UInt(keyLabels.count) if count == 0 { return } + let cellHeight: CGFloat = bounds.size.height / CGFloat(count) - let black = NSColor.black - let darkGray = NSColor(deviceWhite: 0.7, alpha: 1.0) - let lightGray = NSColor(deviceWhite: 0.8, alpha: 1.0) - + let paraStyle = NSMutableParagraphStyle() paraStyle.setParagraphStyle(NSParagraphStyle.default) paraStyle.alignment = .center - + let textAttr: [NSAttributedString.Key: AnyObject] = [ .font: keyLabelFont, - .foregroundColor: black, + .foregroundColor: NSColor.secondaryLabelColor, // The index text color of the non-highlightened candidate + .paragraphStyle: paraStyle] + let textAttrHighlight: [NSAttributedString.Key: AnyObject] = [ + .font: keyLabelFont, + .foregroundColor: NSColor.selectedMenuItemTextColor.withAlphaComponent(0.84), // The index text color of the highlightened candidate .paragraphStyle: paraStyle] for index in 0..= count { cellRect.size.height += 1.0 } - - (index == highlightedIndex ? darkGray : lightGray).setFill() + + (index == highlightedIndex ? NSColor.alternateSelectedControlColor : NSColor.controlBackgroundColor).setFill() // The background color of the candidate (highlightened : non-highlightened) NSBezierPath.fill(cellRect) let text = keyLabels[Int(index)] - (text as NSString).draw(in: textRect, withAttributes: textAttr) + (text as NSString).draw(in: textRect, withAttributes: (index == highlightedIndex ? textAttrHighlight : textAttr)) } } } @@ -91,10 +97,9 @@ fileprivate class VerticalCandidateTableView: NSTableView { } } -private let kCandidateTextPadding = 24.0 -private let kCandidateTextLeftMargin = 8.0 -private let kCandidateTextPaddingWithMandatedTableViewPadding = 18.0 -private let kCandidateTextLeftMarginWithMandatedTableViewPadding = 0.0 +private let kCandidateTextPadding:CGFloat = 24.0 +private let kCandidateTextLeftMargin:CGFloat = 8.0 + @objc (VTVerticalCandidateController) @@ -106,67 +111,85 @@ public class VerticalCandidateController: CandidateController { private var candidateTextPadding: CGFloat = kCandidateTextPadding private var candidateTextLeftMargin: CGFloat = kCandidateTextLeftMargin private var maxCandidateAttrStringWidth: CGFloat = 0 - + public init() { var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] + let panelView = NSView(frame: contentRect) // We need an NSView as a round-cornered container for the candidate panel. let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.contentView = panelView; // Specify the NSView to the panel as its content view. panel.hasShadow = true + panel.isOpaque = false // Again transparentify the panel. Otherwise, the cornerRadius below will be meaningless. + panel.backgroundColor = NSColor.clear // One more insurance to transparentify the panel. + // Rounded panelView container. + panelView.wantsLayer = true + panelView.layer?.borderColor = NSColor.selectedMenuItemTextColor.withAlphaComponent(0.30).cgColor + panelView.layer?.borderWidth = 1 + panelView.layer?.cornerRadius = 6.0 + contentRect.origin = NSPoint.zero var stripRect = contentRect stripRect.size.width = 10.0 keyLabelStripView = VerticalKeyLabelStripView(frame: stripRect) panel.contentView?.addSubview(keyLabelStripView) - + var scrollViewRect = contentRect scrollViewRect.origin.x = stripRect.size.width scrollViewRect.size.width -= stripRect.size.width scrollView = NSScrollView(frame: scrollViewRect) + + scrollView.autohidesScrollers = true // Our aesthetics of UI design has to stay close to Apple. + scrollView.drawsBackground = true // Allow scrollView to draw background. + scrollView.backgroundColor = NSColor.clear // Draw a tramsparent background. scrollView.verticalScrollElasticity = .none - + tableView = NSTableView(frame: contentRect) let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "candidate")) column.dataCell = NSTextFieldCell() + if let dataCell = column.dataCell as? NSTextFieldCell { + dataCell.textColor = NSColor.labelColor // candidate phrase text color for conveniences of customization. + } column.isEditable = false - + candidateTextPadding = kCandidateTextPadding candidateTextLeftMargin = kCandidateTextLeftMargin - + tableView.addTableColumn(column) tableView.intercellSpacing = NSSize(width: 0.0, height: 1.0) tableView.headerView = nil tableView.allowsMultipleSelection = false tableView.allowsEmptySelection = false - - if #available(macOS 10.16, *) { - tableView.style = .fullWidth - candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding - candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding + tableView.backgroundColor = NSColor.clear + tableView.gridColor = NSColor.clear + + if #available(macOS 11.0, *) { + tableView.style = .plain + tableView.enclosingScrollView?.borderType = .noBorder } - + scrollView.documentView = tableView panel.contentView?.addSubview(scrollView) - + let paraStyle = NSMutableParagraphStyle() paraStyle.setParagraphStyle(NSParagraphStyle.default) paraStyle.firstLineHeadIndent = candidateTextLeftMargin paraStyle.lineBreakMode = .byClipping - + candidateTextParagraphStyle = paraStyle - + super.init(window: panel) tableView.dataSource = self tableView.delegate = self tableView.doubleAction = #selector(rowDoubleClicked(_:)) tableView.target = self } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public override func reloadData() { maxCandidateAttrStringWidth = ceil(candidateFont.pointSize * 2.0 + candidateTextPadding) tableView.reloadData() @@ -175,28 +198,28 @@ public class VerticalCandidateController: CandidateController { selectedCandidateIndex = 0 } } - + public override func showNextPage() -> Bool { scrollPageByOne(true) } - + public override func showPreviousPage() -> Bool { scrollPageByOne(false) } - + public override func highlightNextCandidate() -> Bool { moveSelectionByOne(true) } - + public override func highlightPreviousCandidate() -> Bool { moveSelectionByOne(false) } - + public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { guard let delegate = delegate else { return UInt.max } - + let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) if firstVisibleRow != -1 { let result = UInt(firstVisibleRow) + index @@ -204,15 +227,15 @@ public class VerticalCandidateController: CandidateController { return result } } - + return UInt.max } - + public override var selectedCandidateIndex: UInt { get { let selectedRow = tableView.selectedRow return selectedRow == -1 ? UInt.max : UInt(selectedRow) - + } set { guard let delegate = delegate else { @@ -222,7 +245,7 @@ public class VerticalCandidateController: CandidateController { let selectedRow = tableView.selectedRow let labelCount = keyLabels.count let itemCount = delegate.candidateCountForController(self) - + if newIndex == UInt.max { if itemCount == 0 { tableView.deselectAll(self) @@ -230,16 +253,16 @@ public class VerticalCandidateController: CandidateController { } newIndex = 0 } - + var lastVisibleRow = newValue - + if selectedRow != -1 && itemCount > 0 && itemCount > labelCount { if newIndex > selectedRow && (Int(newIndex) - selectedRow) > 1 { lastVisibleRow = min(newIndex + UInt(labelCount) - 1, itemCount - 1) } // no need to handle the backward case: (newIndex < selectedRow && selectedRow - newIndex > 1) } - + if itemCount > labelCount { tableView.scrollRowToVisible(Int(lastVisibleRow)) } @@ -249,11 +272,11 @@ public class VerticalCandidateController: CandidateController { } extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegate { - + public func numberOfRows(in tableView: NSTableView) -> Int { Int(delegate?.candidateCountForController(self) ?? 0) } - + public func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { guard let delegate = delegate else { return nil @@ -266,9 +289,8 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat .font: candidateFont, .paragraphStyle: candidateTextParagraphStyle ]) - + // we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead - // expand the window width if text overflows let boundingRect = attrString.boundingRect(with: NSSize(width: 10240.0, height: 10240.0), options: .usesLineFragmentOrigin) let textWidth = boundingRect.size.width + candidateTextPadding @@ -276,16 +298,16 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat maxCandidateAttrStringWidth = textWidth layoutCandidateView() } - + // keep track of the highlighted index in the key label strip let count = UInt(keyLabels.count) let selectedRow = tableView.selectedRow - + if selectedRow != -1 { var newHilightIndex = 0 - + if keyLabelStripView.highlightedIndex != -1 && - (row >= selectedRow + Int(count) || (selectedRow > count && row <= selectedRow - Int(count))) { + (row >= selectedRow + Int(count) || (selectedRow > count && row <= selectedRow - Int(count))) { newHilightIndex = -1 } else { let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) @@ -294,16 +316,16 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat newHilightIndex = -1 } } - + if newHilightIndex != keyLabelStripView.highlightedIndex && newHilightIndex >= 0 { keyLabelStripView.highlightedIndex = UInt(newHilightIndex) keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame) } - + } return attrString } - + public func tableViewSelectionDidChange(_ notification: Notification) { let selectedRow = tableView.selectedRow if selectedRow != -1 { @@ -311,7 +333,7 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin) keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow) keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame) - + // fix a subtle OS X "bug" that, since we force the scroller to appear, // scrolling sometimes shows a temporarily "broken" scroll bar // (but quickly disappears) @@ -320,14 +342,14 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat } } } - + @objc func rowDoubleClicked(_ sender: Any) { let clickedRow = tableView.clickedRow if clickedRow != -1 { delegate?.candidateController(self, didSelectCandidateAtIndex: UInt(clickedRow)) } } - + func scrollPageByOne(_ forward: Bool) -> Bool { guard let delegate = delegate else { return false @@ -340,7 +362,7 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat if itemCount <= labelCount { return false } - + var newIndex = selectedCandidateIndex if forward { if newIndex == itemCount - 1 { @@ -351,7 +373,7 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat if newIndex == 0 { return false } - + if newIndex < labelCount { newIndex = 0 } else { @@ -361,7 +383,7 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat selectedCandidateIndex = newIndex return true } - + private func moveSelectionByOne(_ forward: Bool) -> Bool { guard let delegate = delegate else { return false @@ -385,13 +407,13 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat selectedCandidateIndex = newIndex return true } - + private func layoutCandidateView() { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { [self] in doLayoutCandidateView() } } - + private func doLayoutCandidateView() { guard let delegate = delegate else { return @@ -400,13 +422,13 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat if 0 == count { return } - + let candidateFontSize = ceil(candidateFont.pointSize) let keyLabelFontSize = ceil(keyLabelFont.pointSize) let fontSize = max(candidateFontSize, keyLabelFontSize) - + let controlSize: NSControl.ControlSize = fontSize > 36.0 ? .regular : .small - + var keyLabelCount = UInt(keyLabels.count) var scrollerWidth: CGFloat = 0.0 if count <= keyLabelCount { @@ -416,40 +438,40 @@ extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegat scrollView.hasVerticalScroller = true let verticalScroller = scrollView.verticalScroller verticalScroller?.controlSize = controlSize - verticalScroller?.scrollerStyle = .legacy - scrollerWidth = NSScroller.scrollerWidth(for: controlSize, scrollerStyle: .legacy) + verticalScroller?.scrollerStyle = .overlay // Aesthetics + scrollerWidth = NSScroller.scrollerWidth(for: controlSize, scrollerStyle: .overlay) // Aesthetics } - + keyLabelStripView.keyLabelFont = keyLabelFont keyLabelStripView.keyLabels = Array(keyLabels[0..= candidateFontSize) ? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0) - + let rowHeight = ceil(fontSize * 1.25) tableView.rowHeight = rowHeight - + var maxKeyLabelWidth = keyLabelFontSize let textAttr: [NSAttributedString.Key: AnyObject] = [.font: keyLabelFont] let boundingBox = NSSize(width: 1600.0, height: 1600.0) - + for label in keyLabels { let rect = (label as NSString).boundingRect(with: boundingBox, options: .usesLineFragmentOrigin, attributes: textAttr) maxKeyLabelWidth = max(rect.size.width, maxKeyLabelWidth) } - + let rowSpacing = tableView.intercellSpacing.height let stripWidth = ceil(maxKeyLabelWidth * 1.20) let tableViewStartWidth = ceil(maxCandidateAttrStringWidth + scrollerWidth) - let windowWidth = stripWidth + 1.0 + tableViewStartWidth + let windowWidth = stripWidth + 0.0 + tableViewStartWidth // Compensation to the removal of the border line between the index labels and the candidate phrase list let windowHeight = CGFloat(keyLabelCount) * (rowHeight + rowSpacing) - + var frameRect = self.window?.frame ?? NSRect.zero let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height) - + frameRect.size = NSMakeSize(windowWidth, windowHeight) frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) - + keyLabelStripView.frame = NSRect(x: 0.0, y: 0.0, width: stripWidth, height: windowHeight) - scrollView.frame = NSRect(x: stripWidth + 1.0, y: 0.0, width: tableViewStartWidth, height: windowHeight) + scrollView.frame = NSRect(x: stripWidth + 0.0, y: 0.0, width: tableViewStartWidth, height: windowHeight) // Remove the border line between the index labels and the candidate phrase list self.window?.setFrame(frameRect, display: false) } } -- Gitee From c4d7007f2962958fbee999cb471982c9f94f935f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 14 Jan 2022 22:52:32 +0800 Subject: [PATCH 036/163] Shiki: Swiftify // Voltaire UI Revamp - Horizontal --- .../HorizontalCandidateController.swift | 213 ++++++++++-------- 1 file changed, 115 insertions(+), 98 deletions(-) diff --git a/Source/CandidateUI/HorizontalCandidateController.swift b/Source/CandidateUI/HorizontalCandidateController.swift index 74bd033d9..61611897f 100644 --- a/Source/CandidateUI/HorizontalCandidateController.swift +++ b/Source/CandidateUI/HorizontalCandidateController.swift @@ -1,11 +1,16 @@ // // HorizontalCandidateController.swift // -// Copyright (c) 2011 The McBopomofo Project. +// Voltaire IME Candidate Controller Module +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// Beautified by (c) 2021-2022 The vChewing Project. // // Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) +// Lukhnos Liu (@lukhnos) @ OpenVanilla // Original Developer +// Weizhong Yang (@zonble) @ OpenVanilla // Rewriter to Swift +// Shiki Suen (ShikiSuen) @ vChewing // Beautification (objC) + // // Based on the Syrup Project and the Formosana Library // by Lukhnos Liu (@lukhnos). @@ -38,112 +43,122 @@ fileprivate class HorizontalCandidateView: NSView { var highlightedIndex: UInt = 0 var action: Selector? weak var target: AnyObject? - + private var keyLabels: [String] = [] private var displayedCandidates: [String] = [] + private var dispCandidatesWithLabels: [String] = [] private var keyLabelHeight: CGFloat = 0 + private var keyLabelWidth: CGFloat = 0 private var candidateTextHeight: CGFloat = 0 private var cellPadding: CGFloat = 0 private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:] + private var candidateWithLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:] private var elementWidths: [CGFloat] = [] private var trackingHighlightedIndex: UInt = UInt.max - + override var isFlipped: Bool { true } - + var sizeForView: NSSize { var result = NSSize.zero - + if !elementWidths.isEmpty { result.width = elementWidths.reduce(0, +) result.width += CGFloat(elementWidths.count) - result.height = keyLabelHeight + candidateTextHeight + 1.0 + // result.height = keyLabelHeight + candidateTextHeight + 1.0 + result.height = candidateTextHeight + cellPadding; } return result } - + @objc (setKeyLabels:displayedCandidates:) func set(keyLabels labels: [String], displayedCandidates candidates: [String]) { let count = min(labels.count, candidates.count) keyLabels = Array(labels[0.. UInt? { let location = convert(event.locationInWindow, to: nil) if !NSPointInRect(location, self.bounds) { @@ -152,16 +167,16 @@ fileprivate class HorizontalCandidateView: NSView { var accuWidth: CGFloat = 0.0 for index in 0..= accuWidth && location.x <= accuWidth + currentWidth { return UInt(index) } accuWidth += currentWidth + 1.0 } return nil - + } - + override func mouseUp(with event: NSEvent) { trackingHighlightedIndex = highlightedIndex guard let newIndex = findHitIndex(event: event) else { @@ -170,7 +185,7 @@ fileprivate class HorizontalCandidateView: NSView { highlightedIndex = newIndex self.setNeedsDisplay(self.bounds) } - + override func mouseDown(with event: NSEvent) { guard let newIndex = findHitIndex(event: event) else { return @@ -181,7 +196,7 @@ fileprivate class HorizontalCandidateView: NSView { } else { highlightedIndex = trackingHighlightedIndex } - + trackingHighlightedIndex = 0 self.setNeedsDisplay(self.bounds) if triggerAction { @@ -198,89 +213,99 @@ public class HorizontalCandidateController: CandidateController { private var prevPageButton: NSButton private var nextPageButton: NSButton private var currentPage: UInt = 0 - + public init() { var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0) let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) - panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) panel.hasShadow = true + panel.isOpaque = false + panel.backgroundColor = NSColor.clear // Transparentify everything outside of the candidate list panel contentRect.origin = NSPoint.zero candidateView = HorizontalCandidateView(frame: contentRect) panel.contentView?.addSubview(candidateView) - contentRect.size = NSSize(width: 36.0, height: 20.0) + contentRect.size = NSSize(width: 20.0, height: 15.0) // Reduce the button width nextPageButton = NSButton(frame: contentRect) nextPageButton.setButtonType(.momentaryLight) - nextPageButton.bezelStyle = .smallSquare - nextPageButton.title = "»" - + nextPageButton.bezelStyle = .shadowlessSquare + nextPageButton.wantsLayer = true + nextPageButton.layer?.masksToBounds = true + nextPageButton.layer?.borderColor = NSColor.clear.cgColor // Attempt to remove the system default layer border color - step 1 + nextPageButton.layer?.borderWidth = 0.0 // Attempt to remove the system default layer border color - step 2 + nextPageButton.layer?.backgroundColor = NSColor.black.cgColor // Button Background Color. Otherwise the button will be half-transparent in macOS Monterey Dark Mode. + nextPageButton.attributedTitle = NSMutableAttributedString(string: "⬇︎") // Next Page Arrow prevPageButton = NSButton(frame: contentRect) prevPageButton.setButtonType(.momentaryLight) - prevPageButton.bezelStyle = .smallSquare - prevPageButton.title = "«" - + prevPageButton.bezelStyle = .shadowlessSquare + prevPageButton.wantsLayer = true + prevPageButton.layer?.masksToBounds = true + prevPageButton.layer?.borderColor = NSColor.clear.cgColor // Attempt to remove the system default layer border color - step 1 + prevPageButton.layer?.borderWidth = 0.0 // Attempt to remove the system default layer border color - step 2 + prevPageButton.layer?.backgroundColor = NSColor.black.cgColor // Button Background Color. Otherwise the button will be half-transparent in macOS Monterey Dark Mode. + prevPageButton.attributedTitle = NSMutableAttributedString(string: "⬆︎") // Previous Page Arrow panel.contentView?.addSubview(nextPageButton) panel.contentView?.addSubview(prevPageButton) - + super.init(window: panel) - + candidateView.target = self candidateView.action = #selector(candidateViewMouseDidClick(_:)) - + nextPageButton.target = self nextPageButton.action = #selector(pageButtonAction(_:)) - + prevPageButton.target = self prevPageButton.action = #selector(pageButtonAction(_:)) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public override func reloadData() { candidateView.highlightedIndex = 0 currentPage = 0 layoutCandidateView() } - + public override func showNextPage() -> Bool { guard delegate != nil else { return false } - + if currentPage + 1 >= pageCount { return false } - + currentPage += 1 candidateView.highlightedIndex = 0 layoutCandidateView() return true } - + public override func showPreviousPage() -> Bool { guard delegate != nil else { return false } - + if currentPage == 0 { return false } - + currentPage -= 1 candidateView.highlightedIndex = 0 layoutCandidateView() return true } - + public override func highlightNextCandidate() -> Bool { guard let delegate = delegate else { return false } - + let currentIndex = selectedCandidateIndex if currentIndex + 1 >= delegate.candidateCountForController(self) { return false @@ -288,30 +313,30 @@ public class HorizontalCandidateController: CandidateController { selectedCandidateIndex = currentIndex + 1 return true } - + public override func highlightPreviousCandidate() -> Bool { guard delegate != nil else { return false } - + let currentIndex = selectedCandidateIndex if currentIndex == 0 { return false } - + selectedCandidateIndex = currentIndex - 1 return true } - + public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt { guard let delegate = delegate else { return UInt.max } - + let result = currentPage * UInt(keyLabels.count) + index return result < delegate.candidateCountForController(self) ? result : UInt.max } - + public override var selectedCandidateIndex: UInt { get { currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex @@ -331,7 +356,7 @@ public class HorizontalCandidateController: CandidateController { } extension HorizontalCandidateController { - + private var pageCount: UInt { guard let delegate = delegate else { return 0 @@ -340,17 +365,17 @@ extension HorizontalCandidateController { let keyLabelCount = UInt(keyLabels.count) return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0) } - + private func layoutCandidateView() { guard let delegate = delegate else { return } - + candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont) var candidates = [String]() let count = delegate.candidateCountForController(self) let keyLabelCount = UInt(keyLabels.count) - + let begin = currentPage * keyLabelCount for index in begin.. 1 { var buttonRect = nextPageButton.frame - var spacing = 0.0 - - if newSize.height < 40.0 { - buttonRect.size.height = floor(newSize.height / 2) - } else { - buttonRect.size.height = 20.0 - } - - if newSize.height >= 60.0 { - spacing = ceil(newSize.height * 0.1) - } - + let spacing:CGFloat = 0.0 + + buttonRect.size.height = floor(newSize.height / 2) + let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0 buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY) nextPageButton.frame = buttonRect - + buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY + buttonRect.size.height + spacing) prevPageButton.frame = buttonRect - + newSize.width += 52.0 nextPageButton.isHidden = false prevPageButton.isHidden = false @@ -390,16 +407,16 @@ extension HorizontalCandidateController { nextPageButton.isHidden = true prevPageButton.isHidden = true } - + frameRect = window?.frame ?? NSRect.zero - + let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height) frameRect.size = newSize frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height) self.window?.setFrame(frameRect, display: false) candidateView.setNeedsDisplay(candidateView.bounds) } - + @objc fileprivate func pageButtonAction(_ sender: Any) { guard let sender = sender as? NSButton else { return @@ -410,9 +427,9 @@ extension HorizontalCandidateController { _ = showPreviousPage() } } - + @objc fileprivate func candidateViewMouseDidClick(_ sender: Any) { delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex) } - + } -- Gitee From 1572ae5d8fce5d49d088acf7c32bba0908699c98 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 14 Jan 2022 22:27:38 +0800 Subject: [PATCH 037/163] Zonble: Swiftify // InputSourceHelper. --- Source/InputSourceHelper.swift | 143 +++++++++++++++++++++ Source/Installer/AppDelegate.m | 12 +- Source/OVInputSourceHelper.h | 50 ------- Source/OVInputSourceHelper.m | 121 ----------------- Source/main.m | 17 +-- Source/vChewingInstaller-Bridging-Header.h | 4 + vChewing.xcodeproj/project.pbxproj | 34 +++-- 7 files changed, 187 insertions(+), 194 deletions(-) create mode 100644 Source/InputSourceHelper.swift delete mode 100644 Source/OVInputSourceHelper.h delete mode 100644 Source/OVInputSourceHelper.m create mode 100644 Source/vChewingInstaller-Bridging-Header.h diff --git a/Source/InputSourceHelper.swift b/Source/InputSourceHelper.swift new file mode 100644 index 000000000..152b1b141 --- /dev/null +++ b/Source/InputSourceHelper.swift @@ -0,0 +1,143 @@ +// +// InputSourceHelper.swift +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla +// Weizhong Yang (@zonble) @ OpenVanilla +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa +import Carbon + +public class InputSourceHelper: NSObject { + + @available(*, unavailable) + public override init() { + super.init() + } + + public static func allInstalledInputSources() -> [TISInputSource] { + TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] + } + + @objc (inputSourceForProperty:stringValue:) + public static func inputSource(for propertyKey: CFString, stringValue: String) -> TISInputSource? { + let stringID = CFStringGetTypeID() + for source in allInstalledInputSources() { + if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) { + let property = Unmanaged.fromOpaque(propertyPtr).takeUnretainedValue() + let typeID = CFGetTypeID(property) + if typeID != stringID { + continue + } + if stringValue == property as? String { + return source + } + } + } + return nil + } + + @objc (inputSourceForInputSourceID:) + public static func inputSource(for sourceID: String) -> TISInputSource? { + inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID) + } + + @objc (inputSourceEnabled:) + public static func inputSourceEnabled(for source: TISInputSource) -> Bool { + if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) { + let value = Unmanaged.fromOpaque(valuePts).takeUnretainedValue() + return value == kCFBooleanTrue + } + return false + } + + @objc (enableInputSource:) + public static func enable(inputSource: TISInputSource) -> Bool { + let status = TISEnableInputSource(inputSource) + return status == noErr + } + + @objc (enableAllInputModesForInputSourceBundleID:) + public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool { + var enabled = false + for source in allInstalledInputSources() { + guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), + let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else { + continue + } + let bundleID = Unmanaged.fromOpaque(bundleIDPtr).takeUnretainedValue() + if String(bundleID) == inputSourceBundleD { + let modeEnabled = self.enable(inputSource: source) + if !modeEnabled { + return false + } + enabled = true + } + } + + return enabled + } + + @objc (enableInputMode:forInputSourceBundleID:) + public static func enable(inputMode modeID: String, for bundleID: String) -> Bool { + for source in allInstalledInputSources() { + guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), + let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else { + continue + } + let inputsSourceBundleID = Unmanaged.fromOpaque(bundleIDPtr).takeUnretainedValue() + let inputsSourceModeID = Unmanaged.fromOpaque(modePtr).takeUnretainedValue() + if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) { + let enabled = enable(inputSource: source) + print("Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)") + return enabled + } + + } + print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)") + return false + + } + + @objc (disableInputSource:) + public static func disable(inputSource: TISInputSource) -> Bool { + let status = TISDisableInputSource(inputSource) + return status == noErr + } + + @objc (registerInputSource:) + public static func registerTnputSource(at url: URL) -> Bool { + let status = TISRegisterInputSource(url as CFURL) + return status == noErr + } + +} + diff --git a/Source/Installer/AppDelegate.m b/Source/Installer/AppDelegate.m index 7f43d2ef7..28cfa0cae 100644 --- a/Source/Installer/AppDelegate.m +++ b/Source/Installer/AppDelegate.m @@ -32,7 +32,7 @@ #import "AppDelegate.h" #import -#import "OVInputSourceHelper.h" +#import "vChewingInstaller-Swift.h" static NSString *const kTargetBin = @"vChewing"; static NSString *const kTargetType = @"app"; @@ -197,13 +197,13 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) { NSURL *imeBundleURL = imeBundle.bundleURL; NSString *imeIdentifier = imeBundle.bundleIdentifier; - TISInputSourceRef inputSource = [OVInputSourceHelper inputSourceForInputSourceID:imeIdentifier]; + TISInputSourceRef inputSource = [InputSourceHelper inputSourceForInputSourceID:imeIdentifier]; // if this IME name is not found in the list of available IMEs if (!inputSource) { NSLog(@"Registering input source %@ at %@.", imeIdentifier, imeBundleURL.absoluteString); // then register - BOOL status = [OVInputSourceHelper registerInputSource:imeBundleURL]; + BOOL status = [InputSourceHelper registerInputSource:imeBundleURL]; if (!status) { NSString *message = [NSString stringWithFormat:NSLocalizedString(@"Cannot register input source %@ at %@.", nil), imeIdentifier, imeBundleURL.absoluteString]; @@ -212,7 +212,7 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) { return; } - inputSource = [OVInputSourceHelper inputSourceForInputSourceID:imeIdentifier]; + inputSource = [InputSourceHelper inputSourceForInputSourceID:imeIdentifier]; // if it still doesn't register successfully, bail. if (!inputSource) { NSString *message = [NSString stringWithFormat:NSLocalizedString(@"Cannot find input source %@ after registration.", nil), imeIdentifier]; @@ -234,10 +234,10 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) { // as the kTISPropertyInputSourceIsEnabled can still be true even if the IME is *not* // enabled in the user's current set of IMEs (which means the IME does not show up in // the user's input menu). - BOOL mainInputSourceEnabled = [OVInputSourceHelper inputSourceEnabled:inputSource]; + BOOL mainInputSourceEnabled = [InputSourceHelper inputSourceEnabled:inputSource]; if (!mainInputSourceEnabled || isMacOS12OrAbove) { - mainInputSourceEnabled = [OVInputSourceHelper enableInputSource:inputSource]; + mainInputSourceEnabled = [InputSourceHelper enableInputSource:inputSource]; if (mainInputSourceEnabled) { NSLog(@"Input method enabled: %@", imeIdentifier); } else { diff --git a/Source/OVInputSourceHelper.h b/Source/OVInputSourceHelper.h deleted file mode 100644 index cc4c89191..000000000 --- a/Source/OVInputSourceHelper.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// OVInputSourceHelper.h -// -// Copyright (c) 2010-2011 Lukhnos D. Liu (lukhnos at lukhnos dot org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import - -@interface OVInputSourceHelper : NSObject -// list all installed input sources -+ (NSArray *)allInstalledInputSources; - -// search for a certain input source -+ (TISInputSourceRef)inputSourceForProperty:(CFStringRef)inPropertyKey stringValue:(NSString *)inValue; - -// shorthand for -inputSourceForProerty:kTISPropertyInputSourceID stringValue: -+ (TISInputSourceRef)inputSourceForInputSourceID:(NSString *)inID; - -// enable/disable an input source (along with all its input modes) -+ (BOOL)inputSourceEnabled:(TISInputSourceRef)inInputSource; -+ (BOOL)enableInputSource:(TISInputSourceRef)inInputSource; -+ (BOOL)enableAllInputModesForInputSourceBundleID:(NSString *)inID; -+ (BOOL)enableInputMode:(NSString *)modeID forInputSourceBundleID:(NSString *)bundleID; -+ (BOOL)disableInputSource:(TISInputSourceRef)inInputSource; - -// register (i.e. make available to Input Source tab in Language & Text Preferences) -// an input source installed in (~)/Library/Input Methods or (~)/Library/Keyboard Layouts/ -+ (BOOL)registerInputSource:(NSURL *)inBundleURL; -@end diff --git a/Source/OVInputSourceHelper.m b/Source/OVInputSourceHelper.m deleted file mode 100644 index 6735545a4..000000000 --- a/Source/OVInputSourceHelper.m +++ /dev/null @@ -1,121 +0,0 @@ -// -// OVInputSourceHelper.m -// -// Copyright (c) 2010-2011 Lukhnos D. Liu (lukhnos at lukhnos dot org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "OVInputSourceHelper.h" - -@implementation OVInputSourceHelper -+ (NSArray *)allInstalledInputSources -{ - CFArrayRef list = TISCreateInputSourceList(NULL, true); - return (__bridge NSArray *)list; -} - -+ (TISInputSourceRef)inputSourceForProperty:(CFStringRef)inPropertyKey stringValue:(NSString *)inValue -{ - CFTypeID stringID = CFStringGetTypeID(); - - for (id source in [self allInstalledInputSources]) { - CFTypeRef property = TISGetInputSourceProperty((__bridge TISInputSourceRef)source, inPropertyKey); - if (!property || CFGetTypeID(property) != stringID) { - continue; - } - - if (inValue && [inValue compare:(__bridge NSString *)property] == NSOrderedSame) { - return (__bridge TISInputSourceRef)source; - } - } - return NULL; -} - -+ (TISInputSourceRef)inputSourceForInputSourceID:(NSString *)inID -{ - return [self inputSourceForProperty:kTISPropertyInputSourceID stringValue:inID]; -} - -+ (BOOL)inputSourceEnabled:(TISInputSourceRef)inInputSource -{ - CFBooleanRef value = TISGetInputSourceProperty(inInputSource, kTISPropertyInputSourceIsEnabled); - return value ? (BOOL)CFBooleanGetValue(value) : NO; -} - -+ (BOOL)enableInputSource:(TISInputSourceRef)inInputSource -{ - OSStatus status = TISEnableInputSource(inInputSource); - return status == noErr; -} - -+ (BOOL)enableAllInputModesForInputSourceBundleID:(NSString *)inID -{ - BOOL enabled = NO; - - for (id source in [self allInstalledInputSources]) { - TISInputSourceRef inputSource = (__bridge TISInputSourceRef)source; - NSString *bundleID = (__bridge NSString *)TISGetInputSourceProperty(inputSource, kTISPropertyBundleID); - NSString *mode = (NSString *)CFBridgingRelease(TISGetInputSourceProperty(inputSource, kTISPropertyInputModeID)); - if (mode && [bundleID isEqualToString:inID]) { - BOOL modeEnabled = [self enableInputSource:inputSource]; - if (!modeEnabled) { - return NO; - } - - enabled = YES; - } - } - - return enabled; -} - -+ (BOOL)enableInputMode:(NSString *)modeID forInputSourceBundleID:(NSString *)bundleID -{ - for (id source in [self allInstalledInputSources]) { - TISInputSourceRef inputSource = (__bridge TISInputSourceRef)source; - NSString *inputSoureBundleID = (__bridge NSString *)TISGetInputSourceProperty(inputSource, kTISPropertyBundleID); - NSString *inputSourceModeID = (NSString *)CFBridgingRelease(TISGetInputSourceProperty(inputSource, kTISPropertyInputModeID)); - - if ([modeID isEqual:inputSourceModeID] && [bundleID isEqual:inputSoureBundleID]) { - BOOL enabled = [self enableInputSource:inputSource]; - NSLog(@"Attempt to enable input source of mode: %@, bundle ID: %@, result: %d", modeID, bundleID, enabled); - return enabled; - } - } - - NSLog(@"Failed to find any matching input source of mode: %@, bundle ID: %@", modeID, bundleID); - return NO; -} - -+ (BOOL)disableInputSource:(TISInputSourceRef)inInputSource -{ - OSStatus status = TISDisableInputSource(inInputSource); - return status == noErr; -} - -+ (BOOL)registerInputSource:(NSURL *)inBundleURL -{ - OSStatus status = TISRegisterInputSource((__bridge CFURLRef)inBundleURL); - return status == noErr; -} -@end diff --git a/Source/main.m b/Source/main.m index 6f1da1603..14bd2c6e9 100644 --- a/Source/main.m +++ b/Source/main.m @@ -37,7 +37,8 @@ // #import -#import "OVInputSourceHelper.h" +#import +#import "vChewing-Swift.h" static NSString *const kConnectionName = @"vChewing_1_Connection"; @@ -58,20 +59,20 @@ int main(int argc, char *argv[]) bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; } - TISInputSourceRef inputSource = [OVInputSourceHelper inputSourceForInputSourceID:bundleID]; + TISInputSourceRef inputSource = [InputSourceHelper inputSourceForInputSourceID:bundleID]; // if this IME name is not found in the list of available IMEs if (!inputSource) { NSLog(@"Registering input source %@ at %@.", bundleID, [bundleURL absoluteString]); // then register - BOOL status = [OVInputSourceHelper registerInputSource:bundleURL]; + BOOL status = [InputSourceHelper registerInputSource:bundleURL]; if (!status) { NSLog(@"Fatal error: Cannot register input source %@ at %@.", bundleID, [bundleURL absoluteString]); return -1; } - inputSource = [OVInputSourceHelper inputSourceForInputSourceID:bundleID]; + inputSource = [InputSourceHelper inputSourceForInputSourceID:bundleID]; // if it still doesn't register successfully, bail. if (!inputSource) { NSLog(@"Fatal error: Cannot find input source %@ after registration.", bundleID); @@ -80,22 +81,22 @@ int main(int argc, char *argv[]) } // if it's not enabled, just enabled it - if (inputSource && ![OVInputSourceHelper inputSourceEnabled:inputSource]) { + if (inputSource && ![InputSourceHelper inputSourceEnabled:inputSource]) { NSLog(@"Enabling input source %@ at %@.", bundleID, [bundleURL absoluteString]); - BOOL status = [OVInputSourceHelper enableInputSource:inputSource]; + BOOL status = [InputSourceHelper enableInputSource:inputSource]; if (!status) { NSLog(@"Fatal error: Cannot enable input source %@.", bundleID); return -1; } - if (![OVInputSourceHelper inputSourceEnabled:inputSource]){ + if (![InputSourceHelper inputSourceEnabled:inputSource]){ NSLog(@"Fatal error: Cannot enable input source %@.", bundleID); return -1; } } if (argc > 2 && !strcmp(argv[2], "--all")) { - BOOL enabled = [OVInputSourceHelper enableAllInputModesForInputSourceBundleID:bundleID]; + BOOL enabled = [InputSourceHelper enableAllInputModesForInputSourceBundleID:bundleID]; if (enabled) { NSLog(@"All input sources enabled for %@", bundleID); } diff --git a/Source/vChewingInstaller-Bridging-Header.h b/Source/vChewingInstaller-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/Source/vChewingInstaller-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 6e83dec46..c4a0bbb66 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC3FB82278492DE0022E99A /* data-chs.txt */; }; + 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; + 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; @@ -23,7 +25,6 @@ 6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */; }; 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */; }; 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC815FC0D6400ABF4B3 /* main.m */; }; - 6A0D4ED415FC0D6400ABF4B3 /* OVInputSourceHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */; }; 6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */; }; 6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; }; 6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; }; @@ -38,7 +39,6 @@ 6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; 6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; }; 6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; }; - 6AB3620D274CA50700AC7547 /* OVInputSourceHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */; }; 6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; 6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */; }; 6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; }; @@ -88,10 +88,12 @@ 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D92763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.xib"; sourceTree = ""; }; + 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewingInstaller-Bridging-Header.h"; sourceTree = ""; }; 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalCandidateController.swift; sourceTree = ""; }; 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; }; 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; }; 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; + 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = ""; }; 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; 5BF4A70327844DD0007DC6E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; @@ -109,8 +111,6 @@ 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputMethodController.h; sourceTree = ""; }; 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = InputMethodController.mm; sourceTree = ""; }; 6A0D4EC815FC0D6400ABF4B3 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 6A0D4EC915FC0D6400ABF4B3 /* OVInputSourceHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVInputSourceHelper.h; sourceTree = ""; }; - 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OVInputSourceHelper.m; sourceTree = ""; }; 6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesWindowController.h; sourceTree = ""; }; 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesWindowController.m; sourceTree = ""; }; 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Bopomofo.tiff; sourceTree = ""; }; @@ -264,6 +264,7 @@ 6A0D4EC215FC0D3C00ABF4B3 /* Source */ = { isa = PBXGroup; children = ( + 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */, 5B58E87D278413E7003EA2AD /* MITLicense.txt */, 6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */, 6A38BBDD15FC115800A8A51F /* Data */, @@ -280,12 +281,11 @@ 6A0D4EF615FC0DA600ABF4B3 /* vChewing-Prefix.pch */, 6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */, 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */, - 6A0D4EC915FC0D6400ABF4B3 /* OVInputSourceHelper.h */, - 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */, 6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */, 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */, D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */, D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */, + 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */, 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */, 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */, ); @@ -530,6 +530,9 @@ 6A0D4EA115FC0D2D00ABF4B3 = { LastSwiftMigration = 1240; }; + 6ACA41CA15FC1D7500935EF6 = { + LastSwiftMigration = 1320; + }; }; }; buildConfigurationList = 6A0D4E9715FC0CFA00ABF4B3 /* Build configuration list for PBXProject "vChewing" */; @@ -621,10 +624,10 @@ buildActionMask = 2147483647; files = ( 6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */, + 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, - 6A0D4ED415FC0D6400ABF4B3 /* OVInputSourceHelper.m in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, 6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */, @@ -643,8 +646,8 @@ files = ( 6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */, 6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */, - 6AB3620D274CA50700AC7547 /* OVInputSourceHelper.m in Sources */, 6ACA41FF15FC1D9000935EF6 /* main.m in Sources */, + 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -901,7 +904,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = "Source/vChewing-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; WRAPPER_EXTENSION = app; }; @@ -1022,6 +1024,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -1046,11 +1049,17 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ../; INFOPLIST_FILE = "Source/Installer/Installer-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = "org.openvanilla.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_OBJC_BRIDGING_HEADER = "Source/vChewingInstaller-Bridging-Header.h"; + SWIFT_VERSION = 5.0; WRAPPER_EXTENSION = app; }; name = Debug; @@ -1061,6 +1070,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -1079,10 +1089,16 @@ GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ../; INFOPLIST_FILE = "Source/Installer/Installer-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "org.openvanilla.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_OBJC_BRIDGING_HEADER = "Source/vChewingInstaller-Bridging-Header.h"; + SWIFT_VERSION = 5.0; WRAPPER_EXTENSION = app; }; name = Release; -- Gitee From 5b1d05564c2a0822ea04f6f5412538d5351f511f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 14 Jan 2022 22:37:05 +0800 Subject: [PATCH 038/163] Zonble: Swiftify // PreferencesWindowController --- Source/AppDelegate.m | 2 +- Source/PreferencesWindowController.h | 50 -------- Source/PreferencesWindowController.m | 139 ---------------------- Source/PreferencesWindowController.swift | 144 +++++++++++++++++++++++ vChewing.xcodeproj/project.pbxproj | 12 +- 5 files changed, 150 insertions(+), 197 deletions(-) delete mode 100644 Source/PreferencesWindowController.h delete mode 100644 Source/PreferencesWindowController.m create mode 100644 Source/PreferencesWindowController.swift diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index 55eca7046..b3282bf8d 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -38,7 +38,7 @@ #import "AppDelegate.h" #import "OVNonModalAlertWindowController.h" -#import "PreferencesWindowController.h" +#import "vChewing-Swift.h" #import "frmAboutWindow.h" extern void LTLoadLanguageModel(void); diff --git a/Source/PreferencesWindowController.h b/Source/PreferencesWindowController.h deleted file mode 100644 index 4d04222f0..000000000 --- a/Source/PreferencesWindowController.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// PreferencesWindowController.h -// -// Copyright (c) 2021 The vChewing Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -#import - -@interface PreferencesWindowController : NSWindowController -{ -@private - NSPopUpButton *__weak _fontSizePopUpButton; - NSPopUpButton *__weak _basisKeyboardLayoutButton; - NSComboBox *__weak _selectionKeyComboBox; -} - -- (IBAction)updateBasisKeyboardLayoutAction:(id)sender; -- (IBAction)changeSelectionKeyAction:(id)sender; - -@property (weak, nonatomic) IBOutlet NSPopUpButton *fontSizePopUpButton; -@property (weak, nonatomic) IBOutlet NSPopUpButton *basisKeyboardLayoutButton; -@property (weak, nonatomic) IBOutlet NSComboBox *selectionKeyComboBox; -@end diff --git a/Source/PreferencesWindowController.m b/Source/PreferencesWindowController.m deleted file mode 100644 index a7ae14edf..000000000 --- a/Source/PreferencesWindowController.m +++ /dev/null @@ -1,139 +0,0 @@ -// -// PreferencesWindowController.m -// -// Copyright (c) 2021 The vChewing Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) -// Weizhong Yang (@zonble) -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -#import "PreferencesWindowController.h" -#import - -static NSString *const kBasisKeyboardLayoutPreferenceKey = @"BasisKeyboardLayout"; // alphanumeric ("ASCII") input basis -static NSString *const kCandidateKeys = @"CandidateKeys"; -static NSString *const kDefaultKeys = @"123456789"; - -@implementation PreferencesWindowController -@synthesize fontSizePopUpButton = _fontSizePopUpButton; -@synthesize basisKeyboardLayoutButton = _basisKeyboardLayoutButton; -@synthesize selectionKeyComboBox = _selectionKeyComboBox; - -- (void)awakeFromNib -{ - CFArrayRef list = TISCreateInputSourceList(NULL, true); - NSMenuItem *usKeyboardLayoutItem = nil; - NSMenuItem *chosenItem = nil; - - [self.basisKeyboardLayoutButton.menu removeAllItems]; - - NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; - - for (int i = 0; i < CFArrayGetCount(list); i++) { - TISInputSourceRef source = (TISInputSourceRef)CFArrayGetValueAtIndex(list, i); - - CFStringRef category = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory); - if (CFStringCompare(category, kTISCategoryKeyboardInputSource, 0) != kCFCompareEqualTo) { - continue; - } - - CFBooleanRef asciiCapable = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable); - if (!CFBooleanGetValue(asciiCapable)) { - continue; - } - - CFStringRef sourceType = TISGetInputSourceProperty(source, kTISPropertyInputSourceType); - if (CFStringCompare(sourceType, kTISTypeKeyboardLayout, 0) != kCFCompareEqualTo) { - continue; - } - - NSString *sourceID = (__bridge NSString *)TISGetInputSourceProperty(source, kTISPropertyInputSourceID); - NSString *localizedName = (__bridge NSString *)TISGetInputSourceProperty(source, kTISPropertyLocalizedName); - - NSMenuItem *item = [[NSMenuItem alloc] init]; - item.title = localizedName; - item.representedObject = sourceID; - - if ([sourceID isEqualToString:@"com.apple.keylayout.US"]) { - usKeyboardLayoutItem = item; - } - - // false if nil - if ([basisKeyboardLayoutID isEqualToString:sourceID]) { - chosenItem = item; - } - - [self.basisKeyboardLayoutButton.menu addItem:item]; - } - - [self.basisKeyboardLayoutButton selectItem:(chosenItem ? chosenItem : usKeyboardLayoutItem)]; - CFRelease(list); - - self.selectionKeyComboBox.usesDataSource = NO; - [self.selectionKeyComboBox removeAllItems]; - [self.selectionKeyComboBox addItemsWithObjectValues:@[ - kDefaultKeys, - @"ASDFGHJKL", - @"ASDFZXCVB" - ]]; - - NSString *ckeys = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeys]; - if (!ckeys || [ckeys stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length == 0) { - ckeys = kDefaultKeys; - } - - [self.selectionKeyComboBox setStringValue:ckeys]; -} - -- (IBAction)updateBasisKeyboardLayoutAction:(id)sender -{ - NSString *sourceID = [[self.basisKeyboardLayoutButton selectedItem] representedObject]; - if (sourceID) { - [[NSUserDefaults standardUserDefaults] setObject:sourceID forKey:kBasisKeyboardLayoutPreferenceKey]; - } -} - -- (IBAction)changeSelectionKeyAction:(id)sender -{ - NSString *keys = [[sender stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - if (keys.length != 9 || - ![keys canBeConvertedToEncoding:NSASCIIStringEncoding]) { - [self.selectionKeyComboBox setStringValue:kDefaultKeys]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:kCandidateKeys]; - NSBeep(); - return; - } - - [self.selectionKeyComboBox setStringValue:keys]; - if ([keys isEqualToString:kDefaultKeys]) { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:kCandidateKeys]; - } else { - [[NSUserDefaults standardUserDefaults] setObject:keys forKey:kCandidateKeys]; - } -} - -@end diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift new file mode 100644 index 000000000..a3ce72d78 --- /dev/null +++ b/Source/PreferencesWindowController.swift @@ -0,0 +1,144 @@ +// +// PreferencesWindowController.swift +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla +// Weizhong Yang (@zonble) @ OpenVanilla +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa +import Carbon + +private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout" +private let kCandidateKeys = "CandidateKeys" +private let kDefaultKeys = "123456789" + +// Please note that the class should be exposed as "PreferencesWindowController" +// in Objective-C in order to let IMK to see the same class name as +// the "InputMethodServerPreferencesWindowControllerClass" in Info.plist. +@objc (PreferencesWindowController) class PreferencesWindowController: NSWindowController { + @IBOutlet weak var fontSizePopUpButton: NSPopUpButton! + @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! + @IBOutlet weak var selectionKeyComboBox: NSComboBox! + + override func awakeFromNib() { + let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] + var usKeyboardLayoutItem: NSMenuItem? = nil + var chosenItem: NSMenuItem? = nil + + basisKeyboardLayoutButton.menu?.removeAllItems() + + let basisKeyboardLayoutID = UserDefaults.standard.string(forKey: kBasisKeyboardLayoutPreferenceKey) + for source in list { + if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { + let category = Unmanaged.fromOpaque(categoryPtr).takeUnretainedValue() + if category != kTISCategoryKeyboardInputSource { + continue + } + } else { + continue + } + + if let asciiCapablePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable) { + let asciiCapable = Unmanaged.fromOpaque(asciiCapablePtr).takeUnretainedValue() + if asciiCapable != kCFBooleanTrue { + continue + } + } else { + continue + } + + if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { + let sourceType = Unmanaged.fromOpaque(sourceTypePtr).takeUnretainedValue() + if sourceType != kTISTypeKeyboardLayout { + continue + } + } else { + continue + } + + guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), + let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else { + continue + } + + let sourceID = String(Unmanaged.fromOpaque(sourceIDPtr).takeUnretainedValue()) + let localizedName = String(Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) + + let menuItem = NSMenuItem() + menuItem.title = localizedName + menuItem.representedObject = sourceID + + if sourceID == "com.apple.keylayout.US" { + usKeyboardLayoutItem = menuItem + } + if basisKeyboardLayoutID == sourceID { + chosenItem = menuItem + } + basisKeyboardLayoutButton.menu?.addItem(menuItem) + } + + basisKeyboardLayoutButton.select(chosenItem ?? usKeyboardLayoutItem) + selectionKeyComboBox.usesDataSource = false + selectionKeyComboBox.addItems(withObjectValues: [kDefaultKeys, "asdfghjkl", "asdfzxcvb"]) + + var candidateSelectionKeys = (UserDefaults.standard.string(forKey: kCandidateKeys) ?? kDefaultKeys) + .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + if candidateSelectionKeys.isEmpty { + candidateSelectionKeys = kDefaultKeys + } + + selectionKeyComboBox.stringValue = candidateSelectionKeys + } + + @IBAction func updateBasisKeyboardLayoutAction(_ sender:Any) { + if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject { + UserDefaults.standard.set(sourceID, forKey: kBasisKeyboardLayoutPreferenceKey) + } + } + + @IBAction func changeSelectionKeyAction(_ sender: Any) { + let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + if keys.count != 9 || !keys.canBeConverted(to: .ascii) { + selectionKeyComboBox.stringValue = kDefaultKeys + UserDefaults.standard.removeObject(forKey: kCandidateKeys) + NSSound.beep() + return + } + + selectionKeyComboBox.stringValue = keys + if keys == kDefaultKeys { + UserDefaults.standard.removeObject(forKey: kCandidateKeys) + } else { + UserDefaults.standard.set(keys, forKey: kCandidateKeys) + } + } + +} diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index c4a0bbb66..6d0fe6c22 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC3FB82278492DE0022E99A /* data-chs.txt */; }; 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; + 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; @@ -25,7 +26,6 @@ 6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */; }; 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */; }; 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC815FC0D6400ABF4B3 /* main.m */; }; - 6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */; }; 6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; }; 6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; }; 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; }; @@ -94,6 +94,7 @@ 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; }; 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = ""; }; + 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; 5BF4A70327844DD0007DC6E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; @@ -111,8 +112,6 @@ 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputMethodController.h; sourceTree = ""; }; 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = InputMethodController.mm; sourceTree = ""; }; 6A0D4EC815FC0D6400ABF4B3 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesWindowController.h; sourceTree = ""; }; - 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesWindowController.m; sourceTree = ""; }; 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Bopomofo.tiff; sourceTree = ""; }; 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "Bopomofo@2x.tiff"; sourceTree = ""; }; 6A0D4EF515FC0DA600ABF4B3 /* vChewing-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "vChewing-Info.plist"; sourceTree = ""; }; @@ -264,7 +263,6 @@ 6A0D4EC215FC0D3C00ABF4B3 /* Source */ = { isa = PBXGroup; children = ( - 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */, 5B58E87D278413E7003EA2AD /* MITLicense.txt */, 6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */, 6A38BBDD15FC115800A8A51F /* Data */, @@ -281,9 +279,9 @@ 6A0D4EF615FC0DA600ABF4B3 /* vChewing-Prefix.pch */, 6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */, 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */, - 6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */, - 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */, + 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */, D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */, + 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */, D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */, 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */, 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */, @@ -630,11 +628,11 @@ 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, - 6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */, 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, + 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */, 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, ); -- Gitee From a70e96a778a49be02d3c2e0f8a70cbf3cde686bf Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 14 Jan 2022 23:44:30 +0800 Subject: [PATCH 039/163] Zonble: Swiftify // NonModalAlertWindowController --- Source/AppDelegate.m | 13 +- Source/InputMethodController.mm | 3 +- Source/NonModalAlertWindowController.swift | 134 ++++++++++++++++++ ....xib => NonModalAlertWindowController.xib} | 60 +++++--- Source/OVNonModalAlertWindowController.h | 31 ---- Source/OVNonModalAlertWindowController.m | 131 ----------------- vChewing.xcodeproj/project.pbxproj | 18 ++- 7 files changed, 191 insertions(+), 199 deletions(-) create mode 100644 Source/NonModalAlertWindowController.swift rename Source/{OVNonModalAlertWindowController.xib => NonModalAlertWindowController.xib} (54%) delete mode 100644 Source/OVNonModalAlertWindowController.h delete mode 100644 Source/OVNonModalAlertWindowController.m diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index b3282bf8d..25248418d 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -37,7 +37,6 @@ // #import "AppDelegate.h" -#import "OVNonModalAlertWindowController.h" #import "vChewing-Swift.h" #import "frmAboutWindow.h" @@ -51,7 +50,7 @@ static NSString *kUpdateInfoSiteKey = @"UpdateInfoSite"; static const NSTimeInterval kNextCheckInterval = 86400.0; static const NSTimeInterval kTimeoutInterval = 60.0; -@interface AppDelegate () +@interface AppDelegate () @end @implementation AppDelegate @@ -159,13 +158,13 @@ static const NSTimeInterval kTimeoutInterval = 60.0; _currentUpdateCheckIsForced = NO; if (isForcedCheck) { - [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Update Check Failed", nil) content:[NSString stringWithFormat:NSLocalizedString(@"There may be no internet connection or the server failed to respond.\n\nError message: %@", nil), [error localizedDescription]] confirmButtonTitle:NSLocalizedString(@"Dismiss", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; + [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Update Check Failed", nil) content:[NSString stringWithFormat:NSLocalizedString(@"There may be no internet connection or the server failed to respond.\n\nError message: %@", nil), [error localizedDescription]] confirmButtonTitle:NSLocalizedString(@"Dismiss", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; } } - (void)showNoUpdateAvailableAlert { - [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Check for Update Completed", nil) content:NSLocalizedString(@"You are already using the latest version of vChewing.", nil) confirmButtonTitle:NSLocalizedString(@"OK", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; + [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Check for Update Completed", nil) content:NSLocalizedString(@"You are already using the latest version of vChewing.", nil) confirmButtonTitle:NSLocalizedString(@"OK", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection @@ -255,7 +254,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0; NSString *content = [NSString stringWithFormat:NSLocalizedString(@"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", nil), [infoDict objectForKey:@"CFBundleShortVersionString"], currentVersion, [plist objectForKey:@"CFBundleShortVersionString"], remoteVersion, versionDescription]; - [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"New Version Available", nil) content:content confirmButtonTitle:NSLocalizedString(@"Visit Website", nil) cancelButtonTitle:NSLocalizedString(@"Not Now", nil) cancelAsDefault:NO delegate:self]; + [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"New Version Available", nil) content:content confirmButtonTitle:NSLocalizedString(@"Visit Website", nil) cancelButtonTitle:NSLocalizedString(@"Not Now", nil) cancelAsDefault:NO delegate:self]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data @@ -263,7 +262,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0; [_receivingData appendData:data]; } -- (void)nonModalAlertWindowControllerDidConfirm:(OVNonModalAlertWindowController *)controller +- (void)nonModalAlertWindowControllerDidConfirm:(NonModalAlertWindowController *)controller { if (_updateNextStepURL) { [[NSWorkspace sharedWorkspace] openURL:_updateNextStepURL]; @@ -272,7 +271,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0; _updateNextStepURL = nil; } -- (void)nonModalAlertWindowControllerDidCancel:(OVNonModalAlertWindowController *)controller +- (void)nonModalAlertWindowControllerDidCancel:(NonModalAlertWindowController *)controller { _updateNextStepURL = nil; } diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 6692bfa71..e8e1adbac 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -43,7 +43,6 @@ #import "OVStringHelper.h" #import "OVUTF8Helper.h" #import "AppDelegate.h" -#import "OVNonModalAlertWindowController.h" #import "vChewing-Swift.h" @@ -1563,7 +1562,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } NSLog(@"openUserPhrases called"); if (!LTCheckIfUserLanguageModelFileExists()) { NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()]; - [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; + [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; return; } diff --git a/Source/NonModalAlertWindowController.swift b/Source/NonModalAlertWindowController.swift new file mode 100644 index 000000000..5be6b1bb7 --- /dev/null +++ b/Source/NonModalAlertWindowController.swift @@ -0,0 +1,134 @@ +// +// NonModalAlertWindowController.swift +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +@objc protocol NonModalAlertWindowControllerDelegate: AnyObject { + func nonModalAlertWindowControllerDidConfirm(_ controller: NonModalAlertWindowController) + func nonModalAlertWindowControllerDidCancel(_ controller: NonModalAlertWindowController) +} + +class NonModalAlertWindowController: NSWindowController { + @objc (sharedInstance) + + static let shared = NonModalAlertWindowController(windowNibName: "NonModalAlertWindowController") + + @IBOutlet weak var titleTextField: NSTextField! + @IBOutlet weak var contentTextField: NSTextField! + @IBOutlet weak var confirmButton: NSButton! + @IBOutlet weak var cancelButton: NSButton! + weak var delegate: NonModalAlertWindowControllerDelegate? + + @objc func show(title: String, content: String, confirmButtonTitle: String, cancelButtonTitle: String?, cancelAsDefault: Bool, delegate: NonModalAlertWindowControllerDelegate?) { + if window?.isVisible == true { + self.delegate?.nonModalAlertWindowControllerDidCancel(self) + } + + self.delegate = delegate + + var oldFrame = confirmButton.frame + confirmButton.title = confirmButtonTitle + confirmButton.sizeToFit() + + var newFrame = confirmButton.frame + newFrame.size.width = max(90, newFrame.size.width + 10) + newFrame.origin.x += oldFrame.size.width - newFrame.size.width + self.confirmButton.frame = newFrame + + if let cancelButtonTitle = cancelButtonTitle { + cancelButton.title = cancelButtonTitle + cancelButton.sizeToFit() + var adjustFrame = cancelButton.frame + adjustFrame.size.width = max(90, adjustFrame.size.width + 10) + adjustFrame.origin.x = newFrame.origin.x - adjustFrame.size.width + confirmButton.frame = adjustFrame + cancelButton.isHidden = false + } else { + cancelButton.isHidden = true + } + + cancelButton.nextKeyView = confirmButton + confirmButton.nextKeyView = cancelButton + + if cancelButtonTitle != nil { + if cancelAsDefault { + window?.defaultButtonCell = cancelButton.cell as? NSButtonCell + } else { + cancelButton.keyEquivalent = " " + window?.defaultButtonCell = confirmButton.cell as? NSButtonCell + } + } else { + window?.defaultButtonCell = confirmButton.cell as? NSButtonCell + } + + titleTextField.stringValue = title + + oldFrame = contentTextField.frame + contentTextField.stringValue = content + + var infiniteHeightFrame = oldFrame + infiniteHeightFrame.size.width -= 4.0 + infiniteHeightFrame.size.height = 10240 + newFrame = (content as NSString).boundingRect(with: infiniteHeightFrame.size, options: [.usesLineFragmentOrigin], attributes: [.font: contentTextField.font!]) + newFrame.size.width = max(newFrame.size.width, oldFrame.size.width) + newFrame.size.height += 4.0 + newFrame.origin = oldFrame.origin + newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height) + contentTextField.frame = newFrame + + var windowFrame = window?.frame ?? NSRect.zero + windowFrame.size.height += (newFrame.size.height - oldFrame.size.height) + window?.level = NSWindow.Level(Int(CGShieldingWindowLevel()) + 1) + window?.setFrame(windowFrame, display: true) + window?.center() + window?.makeKeyAndOrderFront(self) + NSApp.activate(ignoringOtherApps: true) + } + + @IBAction func confirmButtonAction(_ sender: Any) { + delegate?.nonModalAlertWindowControllerDidConfirm(self) + window?.orderOut(self) + } + + @IBAction func cancelButtonAction(_ sender: Any) { + cancel(sender) + } + + func cancel(_ sender: Any) { + delegate?.nonModalAlertWindowControllerDidCancel(self) + delegate = nil + window?.orderOut(self) + } + +} diff --git a/Source/OVNonModalAlertWindowController.xib b/Source/NonModalAlertWindowController.xib similarity index 54% rename from Source/OVNonModalAlertWindowController.xib rename to Source/NonModalAlertWindowController.xib index 430b3b36a..8dc2d753e 100644 --- a/Source/OVNonModalAlertWindowController.xib +++ b/Source/NonModalAlertWindowController.xib @@ -1,12 +1,12 @@ - + - + - + @@ -17,18 +17,20 @@ - - + + - + - - - + - + + + - - - + + + + + - - - + + @@ -72,6 +79,23 @@ + + + + + + + + + + + + + + + + + diff --git a/Source/OVNonModalAlertWindowController.h b/Source/OVNonModalAlertWindowController.h deleted file mode 100644 index e0eea1e25..000000000 --- a/Source/OVNonModalAlertWindowController.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// OVNonModalAlertWindowController.h -// OpenVanilla -// -// Created by Lukhnos Liu on 10/17/12. -// Copyright (c) 2012 The OpenVanilla Project. All rights reserved. -// - -#import - -@class OVNonModalAlertWindowController; - -@protocol OVNonModalAlertWindowControllerDelegate -- (void)nonModalAlertWindowControllerDidConfirm:(OVNonModalAlertWindowController *)controller; - -@optional -- (void)nonModalAlertWindowControllerDidCancel:(OVNonModalAlertWindowController *)controller; -@end - -@interface OVNonModalAlertWindowController : NSWindowController - -+ (OVNonModalAlertWindowController *)sharedInstance; -- (void)showWithTitle:(NSString *)title content:(NSString *)content confirmButtonTitle:(NSString *)confirmTitle cancelButtonTitle:(NSString *)cancelButtonTitle cancelAsDefault:(BOOL)cancelAsDefault delegate:(id)delegate; -- (IBAction)confirmButtonAction:(id)sender; -- (IBAction)cancelButtonAction:(id)sender; -@property (assign, nonatomic) IBOutlet NSTextField *titleTextField; -@property (assign, nonatomic) IBOutlet NSTextField *contentTextField; -@property (assign, nonatomic) IBOutlet NSButton *confirmButton; -@property (assign, nonatomic) IBOutlet NSButton *cancelButton; -@property (assign, nonatomic) id delegate; -@end diff --git a/Source/OVNonModalAlertWindowController.m b/Source/OVNonModalAlertWindowController.m deleted file mode 100644 index 48302d684..000000000 --- a/Source/OVNonModalAlertWindowController.m +++ /dev/null @@ -1,131 +0,0 @@ -// -// OVNonModalAlertWindowController.m -// OpenVanilla -// -// Created by Lukhnos Liu on 10/17/12. -// Copyright (c) 2012 The OpenVanilla Project. All rights reserved. -// - -#import "OVNonModalAlertWindowController.h" - -@implementation OVNonModalAlertWindowController -@synthesize titleTextField = _titleTextField; -@synthesize contentTextField = _contentTextField; -@synthesize confirmButton = _confirmButton; -@synthesize cancelButton = _cancelButton; -@synthesize delegate = _delegate; - -+ (OVNonModalAlertWindowController *)sharedInstance -{ - static OVNonModalAlertWindowController *instance; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[OVNonModalAlertWindowController alloc] initWithWindowNibName:@"OVNonModalAlertWindowController"]; - [instance window]; - }); - return instance; -} - -// Suppress the use of the MIN/MAX macros -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wgnu-statement-expression" - -- (void)showWithTitle:(NSString *)title content:(NSString *)content confirmButtonTitle:(NSString *)confirmTitle cancelButtonTitle:(NSString *)cancelButtonTitle cancelAsDefault:(BOOL)cancelAsDefault delegate:(id )delegate; -{ - // cancel previous alert - if (self.window.visible) { - if ([_delegate respondsToSelector:@selector(nonModalAlertWindowControllerDidCancel:)]) { - [_delegate nonModalAlertWindowControllerDidCancel:self]; - } - } - - _delegate = delegate; - - NSRect oldFrame = self.confirmButton.frame; - [self.confirmButton setTitle:confirmTitle]; - [self.confirmButton sizeToFit]; - - NSRect newFrame = self.confirmButton.frame; - - newFrame.size.width = MAX(90.0, (newFrame.size.width + 10.0)); - newFrame.origin.x += (oldFrame.size.width - newFrame.size.width); - [self.confirmButton setFrame:newFrame]; - - if (cancelButtonTitle) { - [self.cancelButton setTitle:cancelButtonTitle]; - [self.cancelButton sizeToFit]; - NSRect adjustedFrame = self.cancelButton.frame; - adjustedFrame.size.width = MAX(90.0, (adjustedFrame.size.width + 10.0)); - adjustedFrame.origin.x = newFrame.origin.x - adjustedFrame.size.width; - self.cancelButton.frame = adjustedFrame; - self.cancelButton.hidden = NO; - } - else { - self.cancelButton.hidden = YES; - } - - self.cancelButton.nextKeyView = self.confirmButton; - self.confirmButton.nextKeyView = self.cancelButton; - - if (cancelButtonTitle) { - if (cancelAsDefault) { - [self.window setDefaultButtonCell:self.cancelButton.cell]; - } - else { - self.cancelButton.keyEquivalent = @" "; - [self.window setDefaultButtonCell:self.confirmButton.cell]; - } - } - else { - [[self window] setDefaultButtonCell:self.confirmButton.cell]; - } - - self.titleTextField.stringValue = title; - - oldFrame = [self.contentTextField frame]; - self.contentTextField.stringValue = content; - - NSRect infiniteHeightFrame = oldFrame; - infiniteHeightFrame.size.width -= 4.0; - infiniteHeightFrame.size.height = 10240; - newFrame = [content boundingRectWithSize:infiniteHeightFrame.size options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: self.contentTextField.font}]; - newFrame.size.width = MAX(newFrame.size.width, oldFrame.size.width); - newFrame.size.height += 4.0; - newFrame.origin = oldFrame.origin; - newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height); - [self.contentTextField setFrame:newFrame]; - - NSRect windowFrame = [[self window] frame]; - windowFrame.size.height += (newFrame.size.height - oldFrame.size.height); - - self.window.level = CGShieldingWindowLevel() + 1; - [self.window setFrame:windowFrame display:YES]; - [self.window center]; - [self.window makeKeyAndOrderFront:self]; - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; -} - -#pragma GCC diagnostic pop - -- (IBAction)confirmButtonAction:(id)sender -{ - [_delegate nonModalAlertWindowControllerDidConfirm:self]; - [self.window orderOut:self]; -} - -- (IBAction)cancelButtonAction:(id)sender -{ - [self cancel:sender]; -} - -- (void)cancel:(id)sender -{ - if ([_delegate respondsToSelector:@selector(nonModalAlertWindowControllerDidCancel:)]) { - [_delegate nonModalAlertWindowControllerDidCancel:self]; - } - - _delegate = nil; - [self.window orderOut:self]; -} - -@end diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 6d0fe6c22..0ae003b0e 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; + 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */; }; 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; @@ -46,8 +47,7 @@ 6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; }; 6ACA41FF15FC1D9000935EF6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41F415FC1D9000935EF6 /* main.m */; }; 6ACA420215FC1E5200935EF6 /* vChewing.app in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; }; - 6AFF97F2253B299E007F1C49 /* OVNonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */; }; - 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */; }; + 6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */; }; D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */; }; D48550A325EBE689006A204C /* OpenCC in Frameworks */ = {isa = PBXBuildFile; productRef = D48550A225EBE689006A204C /* OpenCC */; }; /* End PBXBuildFile section */ @@ -95,6 +95,7 @@ 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = ""; }; 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; + 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; 5BF4A70327844DD0007DC6E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; @@ -202,9 +203,7 @@ 6ACA41F415FC1D9000935EF6 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Source/Installer/main.m; sourceTree = SOURCE_ROOT; }; 6ACA41F515FC1D9000935EF6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; 6ACA41F715FC1D9000935EF6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; - 6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVNonModalAlertWindowController.h; sourceTree = ""; }; - 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OVNonModalAlertWindowController.xib; sourceTree = ""; }; - 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OVNonModalAlertWindowController.m; sourceTree = ""; }; + 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonModalAlertWindowController.xib; sourceTree = ""; }; D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewing-Bridging-Header.h"; sourceTree = ""; }; D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCCBridge.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -277,8 +276,7 @@ 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, 6A0D4EC815FC0D6400ABF4B3 /* main.m */, 6A0D4EF615FC0DA600ABF4B3 /* vChewing-Prefix.pch */, - 6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */, - 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */, + 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */, 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */, D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */, 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */, @@ -394,7 +392,7 @@ isa = PBXGroup; children = ( 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */, - 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */, + 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */, 6A0D4EEE15FC0DA600ABF4B3 /* Images */, 6A0D4EF515FC0DA600ABF4B3 /* vChewing-Info.plist */, 6A0D4F4815FC0EE100ABF4B3 /* InfoPlist.strings */, @@ -571,7 +569,7 @@ 6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */, 6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */, 6A38BC1515FC117A00A8A51F /* data.txt in Resources */, - 6AFF97F2253B299E007F1C49 /* OVNonModalAlertWindowController.xib in Resources */, + 6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */, 5B58E87F278413E7003EA2AD /* MITLicense.txt in Resources */, 6A187E2616004C5900466B2E /* MainMenu.xib in Resources */, 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */, @@ -628,12 +626,12 @@ 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, - 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */, 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, + 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; -- Gitee From f8d5febadb5e5e5fe46755f66d185f071306c04b Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 00:59:34 +0800 Subject: [PATCH 040/163] Zonble: Swiftify // AppDelegate - Applied the usage of frmAboutWindow and Hiraku's Notification Center implementation. - Disables auto-update by default since this feature is not finished for vChewing. Co-Authored-By: Hiraku --- Source/AppDelegate.h | 61 ------ Source/AppDelegate.m | 296 ----------------------------- Source/AppDelegate.swift | 251 ++++++++++++++++++++++++ Source/InputMethodController.mm | 1 - Source/vChewing-Bridging-Header.h | 2 + vChewing.xcodeproj/project.pbxproj | 10 +- 6 files changed, 257 insertions(+), 364 deletions(-) delete mode 100644 Source/AppDelegate.h delete mode 100644 Source/AppDelegate.m create mode 100644 Source/AppDelegate.swift diff --git a/Source/AppDelegate.h b/Source/AppDelegate.h deleted file mode 100644 index a2c011088..000000000 --- a/Source/AppDelegate.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// AppDelegate.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla -// Weizhong Yang (@zonble) @ OpenVanilla -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Hiraku Wang (@hirakujira) @ vChewing -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import -#import "frmAboutWindow.h" - -@class PreferencesWindowController; - -@interface AppDelegate : NSObject -{ -@private - NSURLConnection *_updateCheckConnection; - BOOL _currentUpdateCheckIsForced; - NSMutableData *_receivingData; - NSURL *_updateNextStepURL; - PreferencesWindowController *_preferencesWindowController; - frmAboutWindow *_aboutWindowController; -} - -- (void)checkForUpdate; -- (void)checkForUpdateForced:(BOOL)forced; -- (void)showPreferences; -- (void)showAbout; - -@property (weak, nonatomic) IBOutlet NSWindow *window; -@end diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m deleted file mode 100644 index 25248418d..000000000 --- a/Source/AppDelegate.m +++ /dev/null @@ -1,296 +0,0 @@ -// -// AppDelegate.m -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla -// Weizhong Yang (@zonble) @ OpenVanilla -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Hiraku Wang (@hirakujira) @ vChewing -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -#import "AppDelegate.h" -#import "vChewing-Swift.h" -#import "frmAboutWindow.h" - -extern void LTLoadLanguageModel(void); -extern void LTLoadUserLanguageModelFile(void); - -static NSString *kCheckUpdateAutomatically = @"CheckUpdateAutomatically"; -static NSString *kNextUpdateCheckDateKey = @"NextUpdateCheckDate"; -static NSString *kUpdateInfoEndpointKey = @"UpdateInfoEndpoint"; -static NSString *kUpdateInfoSiteKey = @"UpdateInfoSite"; -static const NSTimeInterval kNextCheckInterval = 86400.0; -static const NSTimeInterval kTimeoutInterval = 60.0; - -@interface AppDelegate () -@end - -@implementation AppDelegate -@synthesize window = _window; - -- (void)dealloc -{ - _preferencesWindowController = nil; - _aboutWindowController = nil; - _updateCheckConnection = nil; -} - -- (void)applicationDidFinishLaunching:(NSNotification *)inNotification -{ - LTLoadLanguageModel(); - LTLoadUserLanguageModelFile(); - - if (![[NSUserDefaults standardUserDefaults] objectForKey:kCheckUpdateAutomatically]) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kCheckUpdateAutomatically]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } - - [self checkForUpdate]; - [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; -} - -- (void)checkForUpdate -{ - [self checkForUpdateForced:NO]; -} - -- (void)checkForUpdateForced:(BOOL)forced -{ - if (_updateCheckConnection) { - // busy - return; - } - - _currentUpdateCheckIsForced = forced; - - // time for update? - if (!forced) { - if (![[NSUserDefaults standardUserDefaults] boolForKey:kCheckUpdateAutomatically]) { - return; - } - - NSDate *now = [NSDate date]; - NSDate *date = [[NSUserDefaults standardUserDefaults] objectForKey:kNextUpdateCheckDateKey]; - if (![date isKindOfClass:[NSDate class]]) { - date = now; - } - - if ([now compare:date] == NSOrderedAscending) { - return; - } - } - - NSDate *nextUpdateDate = [NSDate dateWithTimeInterval:kNextCheckInterval sinceDate:[NSDate date]]; - [[NSUserDefaults standardUserDefaults] setObject:nextUpdateDate forKey:kNextUpdateCheckDateKey]; - - NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; - NSString *updateInfoURLString = [infoDict objectForKey:kUpdateInfoEndpointKey]; - if (![updateInfoURLString length]) { - return; - } - - NSURL *updateInfoURL = [NSURL URLWithString:updateInfoURLString]; - if (!updateInfoURL) { - return; - } - - NSURLRequest *request = [NSURLRequest requestWithURL:updateInfoURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:kTimeoutInterval]; - if (!request) { - return; - } -#if DEBUG - NSLog(@"about to request update url %@ ",updateInfoURL); -#endif - - if (_receivingData) { - _receivingData = nil; - } - - // create a new data buffer and connection - _receivingData = [[NSMutableData alloc] init]; - _updateCheckConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; - [_updateCheckConnection start]; -} - -- (void)showPreferences -{ - if (!_preferencesWindowController) { - _preferencesWindowController = [[PreferencesWindowController alloc] initWithWindowNibName:@"preferences"]; - } - [[_preferencesWindowController window] center]; - [[_preferencesWindowController window] orderFront:self]; -} - -- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error -{ - BOOL isForcedCheck = _currentUpdateCheckIsForced; - - _receivingData = nil; - _updateCheckConnection = nil; - _currentUpdateCheckIsForced = NO; - - if (isForcedCheck) { - [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Update Check Failed", nil) content:[NSString stringWithFormat:NSLocalizedString(@"There may be no internet connection or the server failed to respond.\n\nError message: %@", nil), [error localizedDescription]] confirmButtonTitle:NSLocalizedString(@"Dismiss", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; - } -} - -- (void)showNoUpdateAvailableAlert -{ - [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Check for Update Completed", nil) content:NSLocalizedString(@"You are already using the latest version of vChewing.", nil) confirmButtonTitle:NSLocalizedString(@"OK", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; -} - -- (void)connectionDidFinishLoading:(NSURLConnection *)connection -{ - id plist = [NSPropertyListSerialization propertyListWithData:_receivingData options:NSPropertyListImmutable format:NULL error:NULL]; -#if DEBUG - NSLog(@"plist %@",plist); -#endif - - BOOL isForcedCheck = _currentUpdateCheckIsForced; - - _receivingData = nil; - _updateCheckConnection = nil; - _currentUpdateCheckIsForced = NO; - - if (!plist) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - - NSString *remoteVersion = [plist objectForKey:(id)kCFBundleVersionKey]; -#if DEBUG - NSLog(@"the remoteversion is %@",remoteVersion); -#endif - if (!remoteVersion) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - - // TODO: Validate info (e.g. bundle identifier) - // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this - - NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; - NSString *currentVersion = [infoDict objectForKey:(id)kCFBundleVersionKey]; - NSComparisonResult result = [currentVersion compare:remoteVersion options:NSNumericSearch]; - - if (result != NSOrderedAscending) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - - - NSString *siteInfoURLString = [plist objectForKey:kUpdateInfoSiteKey]; - if (!siteInfoURLString) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - - NSURL *siteInfoURL = [NSURL URLWithString:siteInfoURLString]; - if (!siteInfoURL) { - if (isForcedCheck) { - [self showNoUpdateAvailableAlert]; - } - return; - } - _updateNextStepURL = siteInfoURL; - - NSDictionary *versionDescriptions = [plist objectForKey:@"Description"]; - NSString *versionDescription = @""; - if ([versionDescriptions isKindOfClass:[NSDictionary class]]) { - NSString *locale = @"en"; - NSArray *supportedLocales = [NSArray arrayWithObjects:@"en", @"zh-Hant", @"zh-Hans", nil]; - NSArray *preferredTags = [NSBundle preferredLocalizationsFromArray:supportedLocales]; - if ([preferredTags count]) { - locale = [preferredTags objectAtIndex:0]; - } - versionDescription = [versionDescriptions objectForKey:locale]; - if (!versionDescription) { - versionDescription = [versionDescriptions objectForKey:@"en"]; - } - - if (!versionDescription) { - versionDescription = @""; - } - else { - versionDescription = [@"\n\n" stringByAppendingString:versionDescription]; - } - } - - NSString *content = [NSString stringWithFormat:NSLocalizedString(@"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", nil), [infoDict objectForKey:@"CFBundleShortVersionString"], currentVersion, [plist objectForKey:@"CFBundleShortVersionString"], remoteVersion, versionDescription]; - - [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"New Version Available", nil) content:content confirmButtonTitle:NSLocalizedString(@"Visit Website", nil) cancelButtonTitle:NSLocalizedString(@"Not Now", nil) cancelAsDefault:NO delegate:self]; -} - -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data -{ - [_receivingData appendData:data]; -} - -- (void)nonModalAlertWindowControllerDidConfirm:(NonModalAlertWindowController *)controller -{ - if (_updateNextStepURL) { - [[NSWorkspace sharedWorkspace] openURL:_updateNextStepURL]; - } - - _updateNextStepURL = nil; -} - -- (void)nonModalAlertWindowControllerDidCancel:(NonModalAlertWindowController *)controller -{ - _updateNextStepURL = nil; -} - -- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification{ - return YES; -} - -- (void)showAbout { - if (!_aboutWindowController) { - _aboutWindowController = [[frmAboutWindow alloc] initWithWindowNibName:@"frmAboutWindow"]; - } - [[_aboutWindowController window] center]; - [[_aboutWindowController window] orderFrontRegardless]; -} - -- (IBAction) about:(id)sender { - [(AppDelegate *)[NSApp delegate] showAbout]; - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; -} - -@end diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift new file mode 100644 index 000000000..963641626 --- /dev/null +++ b/Source/AppDelegate.swift @@ -0,0 +1,251 @@ +// +// AppDelegate.swift +// +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla +// Weizhong Yang (@zonble) @ OpenVanilla +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa +import InputMethodKit + +private let kCheckUpdateAutomatically = "CheckUpdateAutomatically" +private let kNextUpdateCheckDateKey = "NextUpdateCheckDate" +private let kUpdateInfoEndpointKey = "UpdateInfoEndpoint" +private let kUpdateInfoSiteKey = "UpdateInfoSite" +private let kNextCheckInterval: TimeInterval = 86400.0 +private let kTimeoutInterval: TimeInterval = 60.0 + +@objc (AppDelegate) +class AppDelegate: NSObject, NSApplicationDelegate, + NSUserNotificationCenterDelegate, // Hiraku PR#1 + NonModalAlertWindowControllerDelegate { + + @IBOutlet weak var window: NSWindow? + private var preferencesWindowController: PreferencesWindowController? + private var aboutWindowController: frmAboutWindow? // New About Window + private var checkTask: URLSessionTask? + private var updateNextStepURL: URL? + + // 補上 dealloc + deinit { + preferencesWindowController = nil + aboutWindowController = nil + checkTask = nil + updateNextStepURL = nil + } + + func applicationDidFinishLaunching(_ notification: Notification) { + LTLoadLanguageModel() + LTLoadUserLanguageModelFile(); + + if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { + UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically) + UserDefaults.standard.synchronize() + } + checkForUpdate() + NSUserNotificationCenter.default.delegate = self // Hiraku PR#1 + } + + @objc func showPreferences() { + if (preferencesWindowController == nil) { + preferencesWindowController = PreferencesWindowController(windowNibName: "preferences") + } + preferencesWindowController?.window?.center() + preferencesWindowController?.window?.orderFrontRegardless() // 逼著屬性視窗往最前方顯示 + } + + // New About Window + @objc func showAbout() { + if (aboutWindowController == nil) { + aboutWindowController = frmAboutWindow.init(windowNibName: "frmAboutWindow") + } + aboutWindowController?.window?.center() + aboutWindowController?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 + } + + @objc (checkForUpdate) + func checkForUpdate() { + checkForUpdate(forced: false) + } + + @objc (checkForUpdateForced:) + func checkForUpdate(forced: Bool) { + + if checkTask != nil { + // busy + return + } + + // time for update? + if !forced { + if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false { + return + } + let now = Date() + let date = UserDefaults.standard.object(forKey: kNextUpdateCheckDateKey) as? Date ?? now + if now.compare(date) == .orderedAscending { + return + } + } + + let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date()) + UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey) + + guard let infoDict = Bundle.main.infoDictionary, + let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, + let updateInfoURL = URL(string: updateInfoURLString) else { + return + } + + let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval) + + func showNoUpdateAvailableAlert() { + NonModalAlertWindowController.shared.show(title: NSLocalizedString("Check for Update Completed", comment: ""), content: NSLocalizedString("You are already using the latest version of vChewing.", comment: ""), confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) + } + + let task = URLSession.shared.dataTask(with: request) { data, response, error in + defer { + self.checkTask = nil + } + + if let error = error { + if forced { + let title = NSLocalizedString("Update Check Failed", comment: "") + let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), error.localizedDescription) + let buttonTitle = NSLocalizedString("Dismiss", comment: "") + + DispatchQueue.main.async { + NonModalAlertWindowController.shared.show(title: title , content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) + } + } + return + } + + do { + guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], + let remoteVersion = plist[kCFBundleVersionKey] as? String, + let infoDict = Bundle.main.infoDictionary + else { + if forced { + DispatchQueue.main.async { + showNoUpdateAvailableAlert() + } + } + return + } + + // TODO: Validate info (e.g. bundle identifier) + // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this + let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" + let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil) + + if result != .orderedAscending { + if forced { + DispatchQueue.main.async { + showNoUpdateAvailableAlert() + } + } + } + + guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, + let siteInfoURL = URL(string: siteInfoURLString) else { + if forced { + DispatchQueue.main.async { + showNoUpdateAvailableAlert() + } + } + return + } + + self.updateNextStepURL = siteInfoURL + + var versionDescription = "" + let versionDescriptions = plist["Description"] as? [AnyHashable: Any] + if let versionDescriptions = versionDescriptions { + var locale = "en" + let supportedLocales = ["en", "zh-Hant", "zh-Hans"] + let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) + if let first = preferredTags.first { + locale = first + } + versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? "" + if !versionDescription.isEmpty { + versionDescription = "\n\n" + versionDescription + } + } + + let content = String(format: NSLocalizedString("You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", comment: ""), + infoDict["CFBundleShortVersionString"] as? String ?? "", + currentVersion, + plist["CFBundleShortVersionString"] as? String ?? "", + remoteVersion, + versionDescription) + DispatchQueue.main.async { + NonModalAlertWindowController.shared.show(title: NSLocalizedString("New Version Available", comment: "") , content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self) + } + + } catch { + if forced { + DispatchQueue.main.async { + showNoUpdateAvailableAlert() + } + } + } + } + checkTask = task + task.resume() + } + + func nonModalAlertWindowControllerDidConfirm(_ controller: NonModalAlertWindowController) { + if let updateNextStepURL = updateNextStepURL { + NSWorkspace.shared.open(updateNextStepURL) + } + updateNextStepURL = nil + } + + func nonModalAlertWindowControllerDidCancel(_ controller: NonModalAlertWindowController) { + updateNextStepURL = nil + } + + // Hiraku PR#1 + func userNotificationCenter(_ center: NSUserNotificationCenter, shouldPresent notification: NSUserNotification) -> Bool { + return true + } + + // New About Window + @IBAction func about(_ sender: Any) { + (NSApp.delegate as? AppDelegate)?.showAbout() + NSApplication.shared.activate(ignoringOtherApps: true) + } +} diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index e8e1adbac..023292dc2 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -42,7 +42,6 @@ #import #import "OVStringHelper.h" #import "OVUTF8Helper.h" -#import "AppDelegate.h" #import "vChewing-Swift.h" diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index 1b2cb5d6d..1fa833ccb 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -2,3 +2,5 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // +extern void LTLoadLanguageModel(void); +extern void LTLoadUserLanguageModelFile(void); diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 0ae003b0e..7910062dc 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -20,11 +20,11 @@ 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */; }; + 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D052791DA6700838ADB /* AppDelegate.swift */; }; 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; - 6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */; }; 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */; }; 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC815FC0D6400ABF4B3 /* main.m */; }; 6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; }; @@ -96,6 +96,7 @@ 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = ""; }; 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; + 5BDF2D052791DA6700838ADB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; 5BF4A70327844DD0007DC6E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; @@ -108,8 +109,6 @@ 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 6A0D4EC315FC0D6400ABF4B3 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputMethodController.h; sourceTree = ""; }; 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = InputMethodController.mm; sourceTree = ""; }; 6A0D4EC815FC0D6400ABF4B3 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -268,8 +267,7 @@ 6A0D4F1215FC0EB100ABF4B3 /* Engine */, 6ACA41E715FC1D9000935EF6 /* Installer */, 6A0D4F4715FC0EB900ABF4B3 /* Resources */, - 6A0D4EC315FC0D6400ABF4B3 /* AppDelegate.h */, - 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */, + 5BDF2D052791DA6700838ADB /* AppDelegate.swift */, 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */, 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */, 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */, @@ -619,11 +617,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */, 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, + 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, -- Gitee From 92cace1fcec00489390716f51535f799b3557808 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 10:46:02 +0800 Subject: [PATCH 041/163] Shiki: Swiftify // frmAboutWindow ObjC Compatibility - We leave the frmAboutWindow in ObjC until Apple deprecates ObjC. --- Source/vChewing-Bridging-Header.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index 1fa833ccb..5ba232f60 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -2,5 +2,6 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // +#import "frmAboutWindow.h" extern void LTLoadLanguageModel(void); extern void LTLoadUserLanguageModelFile(void); -- Gitee From 7fdae4188d3e8c39a6f9f015960bbfabc823da1f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 12:07:03 +0800 Subject: [PATCH 042/163] Shiki: Swiftify // gitignore update --- .gitignore | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 5bebbc3c3..d68d256a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,14 @@ -build -*.pbxuser +*.dmg *.mode1v3 +*.pbxuser *.tm_build_errors -*.dmg +.build .DS_Store -project.xcworkspace -xcuserdata -Credits.rtf -# the executable we used to count the occurance of a string in a file -# that can be built by make -C Source/Data/bin/C_Version -# C_count.occ.exe .idea -Source/Data/* \ No newline at end of file +.swiftpm +.vscode +build +Credits.rtf +project.xcworkspace +Source/Data/* +xcuserdata \ No newline at end of file -- Gitee From e9e3c63d3332e24f0f2be4ca6ced9f90af3bbabe Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 13:45:43 +0800 Subject: [PATCH 043/163] Shiki: Swiftify // OpenCC Bridge Format Tweak --- Source/OpenCCBridge.swift | 43 ++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/Source/OpenCCBridge.swift b/Source/OpenCCBridge.swift index 6c64695d6..adba4a52f 100644 --- a/Source/OpenCCBridge.swift +++ b/Source/OpenCCBridge.swift @@ -1,3 +1,36 @@ +// +// PreferencesWindowController.swift +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + import Foundation import OpenCC @@ -7,16 +40,16 @@ class OpenCCBridge : NSObject { private static let shared = OpenCCBridge() private var converter: ChineseConverter? - override init() { + private override init() { try? converter = ChineseConverter(options: .twStandardRev) super.init() } - @objc static func convert(_ string:String) -> String? { - return shared.converter?.convert(string) + @objc static func convert(_ string: String) -> String? { + shared.converter?.convert(string) } - private func convert(_ string:String) -> String? { - return converter?.convert(string) + private func convert(_ string: String) -> String? { + converter?.convert(string) } } -- Gitee From d5d42b77f1d90991a5d3c1126aa09be5e0ba21d3 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 17:10:06 +0800 Subject: [PATCH 044/163] Data Update - 20220115 -- Gitee From dc19c195216494b9e95766b4d70b73eee6f40f20 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 12:14:13 +0800 Subject: [PATCH 045/163] Zonble: mgrLanguageModel // Including Custom Excluded Phrases - Retiring Hiraku's PR01 due to upstream changes. --- Source/AppDelegate.swift | 11 +- .../Engine/Gramambular/BlockReadingBuilder.h | 20 +- Source/Engine/Gramambular/LanguageModel.h | 2 +- Source/Engine/{ => LanguageModel}/FastLM.cpp | 2 +- Source/Engine/{ => LanguageModel}/FastLM.h | 2 +- .../LanguageModel}/UserOverrideModel.cpp | 5 + .../LanguageModel}/UserOverrideModel.h | 5 + Source/Engine/LanguageModel/vChewingLM.cpp | 134 +++++ Source/Engine/LanguageModel/vChewingLM.h | 67 +++ Source/InputMethodController.h | 15 +- Source/InputMethodController.mm | 467 +++++++----------- Source/LanguageModelManager.h | 60 +++ Source/LanguageModelManager.mm | 191 +++++++ Source/Shit4Migration.txt | 71 +++ Source/en.lproj/Localizable.strings | 1 + Source/vChewing-Bridging-Header.h | 8 +- Source/zh-Hans.lproj/Localizable.strings | 1 + Source/zh-Hant.lproj/Localizable.strings | 1 + vChewing.xcodeproj/project.pbxproj | 30 +- 19 files changed, 766 insertions(+), 327 deletions(-) rename Source/Engine/{ => LanguageModel}/FastLM.cpp (99%) rename Source/Engine/{ => LanguageModel}/FastLM.h (96%) rename Source/{ => Engine/LanguageModel}/UserOverrideModel.cpp (97%) rename Source/{ => Engine/LanguageModel}/UserOverrideModel.h (94%) create mode 100644 Source/Engine/LanguageModel/vChewingLM.cpp create mode 100644 Source/Engine/LanguageModel/vChewingLM.h create mode 100644 Source/LanguageModelManager.h create mode 100644 Source/LanguageModelManager.mm create mode 100644 Source/Shit4Migration.txt diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 963641626..d29096a13 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -48,7 +48,6 @@ private let kTimeoutInterval: TimeInterval = 60.0 @objc (AppDelegate) class AppDelegate: NSObject, NSApplicationDelegate, - NSUserNotificationCenterDelegate, // Hiraku PR#1 NonModalAlertWindowControllerDelegate { @IBOutlet weak var window: NSWindow? @@ -66,15 +65,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, } func applicationDidFinishLaunching(_ notification: Notification) { - LTLoadLanguageModel() - LTLoadUserLanguageModelFile(); + LanguageModelManager.loadDataModels() + LanguageModelManager.loadUserPhrasesModel() if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically) UserDefaults.standard.synchronize() } checkForUpdate() - NSUserNotificationCenter.default.delegate = self // Hiraku PR#1 } @objc func showPreferences() { @@ -238,11 +236,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, updateNextStepURL = nil } - // Hiraku PR#1 - func userNotificationCenter(_ center: NSUserNotificationCenter, shouldPresent notification: NSUserNotification) -> Bool { - return true - } - // New About Window @IBAction func about(_ sender: Any) { (NSApp.delegate as? AppDelegate)?.showAbout() diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index 43fa3ba50..a8bf87eea 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -38,7 +38,7 @@ namespace Formosa { class BlockReadingBuilder { public: - BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM); + BlockReadingBuilder(LanguageModel *inLM); void clear(); size_t length() const; @@ -73,13 +73,11 @@ namespace Formosa { Grid m_grid; LanguageModel *m_LM; - LanguageModel *m_UserPhraseLM; string m_joinSeparator; }; - inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM, LanguageModel *inUserPhraseLM) + inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM) : m_LM(inLM) - , m_UserPhraseLM(inUserPhraseLM) , m_cursorIndex(0) , m_markerCursorIndex(SIZE_MAX) { @@ -222,19 +220,7 @@ namespace Formosa { for (size_t q = 1 ; q <= MaximumBuildSpanLength && p+q <= end ; q++) { string combinedReading = Join(m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator); if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q, combinedReading)) { - vector unigrams; - - if (m_UserPhraseLM != NULL) { - if (m_UserPhraseLM->hasUnigramsForKey(combinedReading)) { - vector userUnigrams = m_UserPhraseLM->unigramsForKeys(combinedReading); - unigrams.insert(unigrams.end(), userUnigrams.begin(), userUnigrams.end()); - } - } - - if (m_LM->hasUnigramsForKey(combinedReading)) { - vector globalUnigrams = m_LM->unigramsForKeys(combinedReading); - unigrams.insert(unigrams.end(), globalUnigrams.begin(), globalUnigrams.end()); - } + vector unigrams = m_LM->unigramsForKey(combinedReading); if (unigrams.size() > 0) { Node n(combinedReading, unigrams, vector()); diff --git a/Source/Engine/Gramambular/LanguageModel.h b/Source/Engine/Gramambular/LanguageModel.h index 46c2e1d46..65331b37b 100644 --- a/Source/Engine/Gramambular/LanguageModel.h +++ b/Source/Engine/Gramambular/LanguageModel.h @@ -42,7 +42,7 @@ namespace Formosa { virtual ~LanguageModel() {} virtual const vector bigramsForKeys(const string &preceedingKey, const string& key) = 0; - virtual const vector unigramsForKeys(const string &key) = 0; + virtual const vector unigramsForKey(const string &key) = 0; virtual bool hasUnigramsForKey(const string& key) = 0; }; } diff --git a/Source/Engine/FastLM.cpp b/Source/Engine/LanguageModel/FastLM.cpp similarity index 99% rename from Source/Engine/FastLM.cpp rename to Source/Engine/LanguageModel/FastLM.cpp index 7d759c7ab..dc5423370 100644 --- a/Source/Engine/FastLM.cpp +++ b/Source/Engine/LanguageModel/FastLM.cpp @@ -302,7 +302,7 @@ const vector FastLM::bigramsForKeys(const string& preceedingKey, const s return vector(); } -const vector FastLM::unigramsForKeys(const string& key) +const vector FastLM::unigramsForKey(const string& key) { vector v; map >::const_iterator i = keyRowMap.find(key.c_str()); diff --git a/Source/Engine/FastLM.h b/Source/Engine/LanguageModel/FastLM.h similarity index 96% rename from Source/Engine/FastLM.h rename to Source/Engine/LanguageModel/FastLM.h index 77aac3970..69fb8bd9d 100644 --- a/Source/Engine/FastLM.h +++ b/Source/Engine/LanguageModel/FastLM.h @@ -50,7 +50,7 @@ namespace Formosa { void dump(); virtual const vector bigramsForKeys(const string& preceedingKey, const string& key); - virtual const vector unigramsForKeys(const string& key); + virtual const vector unigramsForKey(const string& key); virtual bool hasUnigramsForKey(const string& key); protected: diff --git a/Source/UserOverrideModel.cpp b/Source/Engine/LanguageModel/UserOverrideModel.cpp similarity index 97% rename from Source/UserOverrideModel.cpp rename to Source/Engine/LanguageModel/UserOverrideModel.cpp index 6c10ebea6..37b222cf9 100644 --- a/Source/UserOverrideModel.cpp +++ b/Source/Engine/LanguageModel/UserOverrideModel.cpp @@ -4,6 +4,11 @@ // Copyright (c) 2021-2022 The vChewing Project. // Copyright (c) 2011-2022 The OpenVanilla Project. // +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing +// // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without diff --git a/Source/UserOverrideModel.h b/Source/Engine/LanguageModel/UserOverrideModel.h similarity index 94% rename from Source/UserOverrideModel.h rename to Source/Engine/LanguageModel/UserOverrideModel.h index f582583eb..3159e74eb 100644 --- a/Source/UserOverrideModel.h +++ b/Source/Engine/LanguageModel/UserOverrideModel.h @@ -4,6 +4,11 @@ // Copyright (c) 2021-2022 The vChewing Project. // Copyright (c) 2011-2022 The OpenVanilla Project. // +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing +// // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without diff --git a/Source/Engine/LanguageModel/vChewingLM.cpp b/Source/Engine/LanguageModel/vChewingLM.cpp new file mode 100644 index 000000000..45b11683d --- /dev/null +++ b/Source/Engine/LanguageModel/vChewingLM.cpp @@ -0,0 +1,134 @@ +// +// vChewingLM.cpp +// +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#include "vChewingLM.h" +#include +#include +#include + +using namespace vChewing; + +vChewingLM::vChewingLM() +{ +} + +vChewingLM::~vChewingLM() +{ + m_languageModel.close(); + m_userPhrases.close(); + m_excludedPhrases.close(); +} + +void vChewingLM::loadLanguageModel(const char* languageModelDataPath) +{ + if (languageModelDataPath) { + m_languageModel.close(); + m_languageModel.open(languageModelDataPath); + } +} + +void vChewingLM::loadUserPhrases(const char* userPhrasesDataPath, + const char* excludedPhrasesDataPath) +{ + if (userPhrasesDataPath) { + m_userPhrases.close(); + m_userPhrases.open(userPhrasesDataPath); + } + if (excludedPhrasesDataPath) { + m_excludedPhrases.close(); + m_excludedPhrases.open(excludedPhrasesDataPath); + } +} + +const vector vChewingLM::bigramsForKeys(const string& preceedingKey, const string& key) +{ + return vector(); +} + +const vector vChewingLM::unigramsForKey(const string& key) +{ + vector unigrams; + vector userUnigrams; + + // Use unordered_set so that you don't have to do O(n*m) + unordered_set excludedValues; + unordered_set userValues; + + if (m_excludedPhrases.hasUnigramsForKey(key)) { + vector excludedUnigrams = m_excludedPhrases.unigramsForKey(key); + transform(excludedUnigrams.begin(), excludedUnigrams.end(), + inserter(excludedValues, excludedValues.end()), + [](const Unigram &u) { return u.keyValue.value; }); + } + + if (m_userPhrases.hasUnigramsForKey(key)) { + vector rawUserUnigrams = m_userPhrases.unigramsForKey(key); + + for (auto&& unigram : rawUserUnigrams) { + if (excludedValues.find(unigram.keyValue.value) == excludedValues.end()) { + userUnigrams.push_back(unigram); + } + } + + transform(userUnigrams.begin(), userUnigrams.end(), + inserter(userValues, userValues.end()), + [](const Unigram &u) { return u.keyValue.value; }); + } + + if (m_languageModel.hasUnigramsForKey(key)) { + vector globalUnigrams = m_languageModel.unigramsForKey(key); + + for (auto&& unigram : globalUnigrams) { + if (excludedValues.find(unigram.keyValue.value) == excludedValues.end() && + userValues.find(unigram.keyValue.value) == userValues.end()) { + unigrams.push_back(unigram); + } + } + } + + unigrams.insert(unigrams.begin(), userUnigrams.begin(), userUnigrams.end()); + return unigrams; +} + +bool vChewingLM::hasUnigramsForKey(const string& key) +{ + if (!m_excludedPhrases.hasUnigramsForKey(key)) { + return m_userPhrases.hasUnigramsForKey(key) || + m_languageModel.hasUnigramsForKey(key); + } + + return unigramsForKey(key).size() > 0; +} diff --git a/Source/Engine/LanguageModel/vChewingLM.h b/Source/Engine/LanguageModel/vChewingLM.h new file mode 100644 index 000000000..29fb1587b --- /dev/null +++ b/Source/Engine/LanguageModel/vChewingLM.h @@ -0,0 +1,67 @@ +// +// vChewingLM.h +// +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#ifndef VCHEWINGLM_H +#define VCHEWINGLM_H + +#include +#include "FastLM.h" + +namespace vChewing { + +using namespace Formosa::Gramambular; + +class vChewingLM : public LanguageModel { +public: + vChewingLM(); + ~vChewingLM(); + + void loadLanguageModel(const char* languageModelDataPath); + void loadUserPhrases(const char* m_userPhrasesDataPath, + const char* m_excludedPhrasesDataPath); + + const vector bigramsForKeys(const string& preceedingKey, const string& key); + const vector unigramsForKey(const string& key); + bool hasUnigramsForKey(const string& key); + +protected: + FastLM m_languageModel; + FastLM m_userPhrases; + FastLM m_excludedPhrases; +}; +}; + +#endif diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index c3f512d72..7fc13239f 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -40,7 +40,7 @@ #import #import "Mandarin.h" #import "Gramambular.h" -#import "FastLM.h" +#import "vChewingLM.h" #import "UserOverrideModel.h" #import "frmAboutWindow.h" @@ -51,8 +51,10 @@ Formosa::Mandarin::BopomofoReadingBuffer* _bpmfReadingBuffer; // language model - Formosa::Gramambular::FastLM *_languageModel; - Formosa::Gramambular::FastLM *_userPhrasesModel; + vChewing::vChewingLM *_languageModel; + + // user override model + vChewing::UserOverrideModel *_userOverrideModel; // the grid (lattice) builder for the unigrams (and bigrams) Formosa::Gramambular::BlockReadingBuilder* _builder; @@ -81,12 +83,5 @@ // if Chinese conversion is enabled BOOL _chineseConversionEnabled; - - // if Chinese conversion status has been changed - BOOL _previousChineseConversionEnabledStatus; } @end - -// the shared language model object -extern "C" void LTLoadLanguageModel(); -extern "C" void LTLoadUserLanguageModelFile(); diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 023292dc2..c07c518de 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -42,19 +42,20 @@ #import #import "OVStringHelper.h" #import "OVUTF8Helper.h" +#import "LanguageModelManager.h" #import "vChewing-Swift.h" - -//@import SwiftUI; +@import OpenCC; // C++ namespace usages using namespace std; using namespace Formosa::Mandarin; using namespace Formosa::Gramambular; +using namespace vChewing; using namespace OpenVanilla; // default, min and max candidate list text size -static const NSInteger kDefaultCandidateListTextSize = 18; +static const NSInteger kDefaultCandidateListTextSize = 16; static const NSInteger kMinKeyLabelSize = 10; static const NSInteger kMinCandidateListTextSize = 12; static const NSInteger kMaxCandidateListTextSize = 196; @@ -113,76 +114,6 @@ VTCandidateController *gCurrentCandidateController = nil; static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; #endif -// shared language model object that stores our phrase-term probability database -FastLM gLanguageModelCHT; -FastLM gLanguageModelCHS; -FastLM gUserPhraseLanguageModelCHT; -FastLM gUserPhraseLanguageModelCHS; - -static const int kUserOverrideModelCapacity = 500; -static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. -vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife); -vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife); - -static NSString *LTUserDataFolderPath() -{ - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES); - NSString *appSupportPath = [paths objectAtIndex:0]; - NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"]; - return userDictPath; -} - -static NSString *LTUserPhrasesDataPathCHT() -{ - return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-cht.txt"]; -} - -static NSString *LTUserPhrasesDataPathCHS() -{ - return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-chs.txt"]; -} - -static BOOL LTCheckIfUserLanguageModelFileExists() { - - NSString *folderPath = LTUserDataFolderPath(); - BOOL isFolder = NO; - BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; - if (folderExist && !isFolder) { - NSError *error = nil; - [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; - if (error) { - NSLog(@"Failed to remove folder %@", error); - return NO; - } - folderExist = NO; - } - if (!folderExist) { - NSError *error = nil; - [[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error]; - if (error) { - NSLog(@"Failed to create folder %@", error); - return NO; - } - } - NSString *filePathCHS = LTUserPhrasesDataPathCHS(); - if (![[NSFileManager defaultManager] fileExistsAtPath:filePathCHS]) { - BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePathCHS atomically:YES]; - if (!result) { - NSLog(@"Failed to write userdict CHS file"); - return NO; - } - } - NSString *filePathCHT = LTUserPhrasesDataPathCHT(); - if (![[NSFileManager defaultManager] fileExistsAtPath:filePathCHT]) { - BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePathCHT atomically:YES]; - if (!result) { - NSLog(@"Failed to write userdict CHT file"); - return NO; - } - } - return YES; -} - // https://clang-analyzer.llvm.org/faq.html __attribute__((annotate("returns_localized_nsstring"))) static inline NSString *LocalizationNotNeeded(NSString *s) { @@ -190,19 +121,12 @@ static inline NSString *LocalizationNotNeeded(NSString *s) { } // private methods -@interface vChewingInputMethodController () +@interface vChewingInputMethodController () + (VTHorizontalCandidateController *)horizontalCandidateController; + (VTVerticalCandidateController *)verticalCandidateController; +@end -- (void)collectCandidates; - -- (size_t)actualCandidateCursorIndex; - -- (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client; - -- (void)beep; -- (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client; -- (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode; +@interface vChewingInputMethodController (VTCandidateController) @end // sort helper @@ -240,8 +164,6 @@ static double FindHighestScore(const vector& nodes, double epsilon) } // the two client pointers are weak pointers (i.e. we don't retain them) // therefore we don't do anything about it - - [[NSNotificationCenter defaultCenter] removeObserver:self name:@"ChineseConversionStatusChanged" object:nil]; } - (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)client @@ -257,10 +179,10 @@ static double FindHighestScore(const vector& nodes, double epsilon) _bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout()); // create the lattice builder - _languageModel = &gLanguageModelCHT; - _userPhrasesModel = &gUserPhraseLanguageModelCHT; - _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); - _uom = &gUserOverrideModelCHT; + _languageModel = [LanguageModelManager languageModelBopomofo]; + _userOverrideModel = [LanguageModelManager userOverrideModel]; + + _builder = new BlockReadingBuilder(_languageModel); // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -270,10 +192,6 @@ static double FindHighestScore(const vector& nodes, double epsilon) _inputMode = kBopomofoModeIdentifier; _chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; - _previousChineseConversionEnabledStatus = _chineseConversionEnabled; - - [[NSNotificationCenter defaultCenter] removeObserver:self name:@"ChineseConversionStatusChanged" object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleChineseConversionStatusDidChanged:) name:@"ChineseConversionStatusChanged" object:nil]; } return self; @@ -287,31 +205,34 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItem:preferenceMenuItem]; NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; - chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand; + chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; [menu addItem:chineseConversionMenuItem]; - [menu addItem:[NSMenuItem separatorItem]]; // ----------------------- - - NSMenuItem *editUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; - [editUserPheaseItem setIndentationLevel:2]; - [menu addItem:editUserPheaseItem]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""]; + + if (_inputMode == kSimpBopomofoModeIdentifier) { + NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesSimpBopomofo:) keyEquivalent:@""]; + [menu addItem:editExcludedPhrasesItem]; + } + else { + NSMenuItem *editUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; + [menu addItem:editUserPhrasesItem]; + + NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesvChewing:) keyEquivalent:@""]; + [menu addItem:editExcludedPhrasesItem]; + } - NSMenuItem *reloadUserPheaseItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; - [reloadUserPheaseItem setIndentationLevel:2]; - [menu addItem:reloadUserPheaseItem]; + NSMenuItem *reloadUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; + [menu addItem:reloadUserPhrasesItem]; + [menu addItem:[NSMenuItem separatorItem]]; - [menu addItem:[NSMenuItem separatorItem]]; // ----------------------- - NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; [menu addItem:updateCheckItem]; NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""]; [menu addItem:aboutMenuItem]; - - // Menu Debug Purposes... - NSLog(@"menu %@", menu); - return menu; } @@ -406,28 +327,22 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)setValue:(id)value forTag:(long)tag client:(id)sender { NSString *newInputMode; - Formosa::Gramambular::FastLM *newLanguageModel; - Formosa::Gramambular::FastLM *userPhraseModel; - vChewing::UserOverrideModel *newUom; + vChewingLM *newLanguageModel; if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) { newInputMode = kSimpBopomofoModeIdentifier; - newLanguageModel = &gLanguageModelCHS; - newUom = &gUserOverrideModelCHS; - userPhraseModel = &gUserPhraseLanguageModelCHS; + newLanguageModel = [LanguageModelManager languageModelSimpBopomofo]; } else { newInputMode = kBopomofoModeIdentifier; - newLanguageModel = &gLanguageModelCHT; - newUom = &gUserOverrideModelCHT; - userPhraseModel = &gUserPhraseLanguageModelCHT; + newLanguageModel = [LanguageModelManager languageModelBopomofo]; } // Only apply the changes if the value is changed if (![_inputMode isEqualToString:newInputMode]) { [[NSUserDefaults standardUserDefaults] synchronize]; - // Remember to override the keyboard layout again -- treat this as an activate event + // Remember to override the keyboard layout again -- treat this as an activate eventy NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; if (!basisKeyboardLayoutID) { basisKeyboardLayoutID = @"com.apple.keylayout.US"; @@ -436,8 +351,6 @@ static double FindHighestScore(const vector& nodes, double epsilon) _inputMode = newInputMode; _languageModel = newLanguageModel; - _userPhrasesModel = userPhraseModel; - _uom = newUom; if (!_bpmfReadingBuffer->isEmpty()) { _bpmfReadingBuffer->clear(); @@ -450,7 +363,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) if (_builder) { delete _builder; - _builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel); + _builder = new BlockReadingBuilder(_languageModel); _builder->setJoinSeparator("-"); } } @@ -463,8 +376,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) // if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper) // then we defer the update in the next runloop round -- so that the composing buffer is not // meaninglessly flushed, an annoying bug in Terminal.app since Mac OS X 10.5 - if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) - { + if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) { if (_currentDeferredClient) { [self performSelector:@selector(updateClientComposingBuffer:) withObject:_currentDeferredClient afterDelay:0.0]; } @@ -563,7 +475,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // i.e. the client app needs to take care of where to put ths composing buffer [client setMarkedText:attrString selectionRange:NSMakeRange((NSInteger)_builder->markerCursorIndex(), 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; _latestReadingCursor = (NSInteger)_builder->markerCursorIndex(); - } else { + } + else { // we must use NSAttributedString so that the cursor is visible -- // can't just use NSString NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), @@ -591,13 +504,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } reverse(_walkedNodes.begin(), _walkedNodes.end()); // if DEBUG is defined, a GraphViz file is written to kGraphVizOutputfile - #if DEBUG +#if DEBUG string dotDump = _builder->grid().dumpDOT(); NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()]; NSError *error = nil; BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile atomically:YES encoding:NSUTF8StringEncoding error:&error]; - #endif +#endif } - (void)popOverflowComposingTextAndWalk:(id)client @@ -695,12 +608,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); - NSString *reading = [_composingBuffer substringWithRange:range]; + NSString *phrase = [_composingBuffer substringWithRange:range]; NSMutableString *string = [[NSMutableString alloc] init]; - [string appendString:reading]; + [string appendString:phrase]; [string appendString:@" "]; NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; - vector v = _builder->readingsAtRange(begin,end); + vector v = _builder->readingsAtRange(begin, end); for(vector::iterator it_i=v.begin(); it_i!=v.end(); ++it_i) { [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; } @@ -712,28 +625,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (BOOL)_writeUserPhrase { - if (!LTCheckIfUserLanguageModelFileExists()) { - return NO; - } - NSString *currentMarkedPhrase = [self _currentMarkedText]; if (![currentMarkedPhrase length]) { return NO; } - currentMarkedPhrase = [currentMarkedPhrase stringByAppendingString:@"\n"]; - - NSString *path = _inputMode == kSimpBopomofoModeIdentifier ? LTUserPhrasesDataPathCHS() : LTUserPhrasesDataPathCHT(); - NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; - if (!file) { - return NO; - } - [file seekToEndOfFile]; - NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; - [file writeData:data]; - [file closeFile]; - LTLoadUserLanguageModelFile(); - return YES; + return [LanguageModelManager writeUserPhrase:currentMarkedPhrase]; } - (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client @@ -831,7 +728,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (charCode == 13) { if ([self _writeUserPhrase]) { _builder->setMarkerCursorIndex(SIZE_MAX); - } else { + } + else { [self beep]; } [self updateClientComposingBuffer:client]; @@ -862,13 +760,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } _builder->setMarkerCursorIndex(SIZE_MAX); } - + // see if it's valid BPMF reading if (_bpmfReadingBuffer->isValidKey((char)charCode)) { _bpmfReadingBuffer->combineKey((char)charCode); - // if we have a tone marker, we have to insert the reading to the builder - // in other words, if we don't have a tone marker, we just update the composing buffer + // if we have a tone marker, we have to insert the reading to the + // builder in other words, if we don't have a tone marker, we just + // update the composing buffer composeReading = _bpmfReadingBuffer->hasToneMarker(); if (!composeReading) { [self updateClientComposingBuffer:client]; @@ -897,8 +796,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self popOverflowComposingTextAndWalk:client]; // get user override model suggestion - string overrideValue = - _uom->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + string overrideValue = (_inputMode == kSimpBopomofoModeIdentifier) ? "" : + _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + if (!overrideValue.empty()) { size_t cursorIndex = [self actualCandidateCursorIndex]; vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); @@ -910,6 +810,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } _bpmfReadingBuffer->clear(); [self updateClientComposingBuffer:client]; + if (_inputMode == kSimpBopomofoModeIdentifier) { + [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; + } + // and tells the client that the key is consumed return YES; } @@ -939,39 +843,39 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // Esc if (charCode == 27) { - BOOL escToClearInputBufferEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kEscToCleanInputBufferKey]; + BOOL escToClearInputBufferEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kEscToCleanInputBufferKey]; + + if (escToClearInputBufferEnabled) { + // if the optioon is enabled, we clear everythiong including the composing + // buffer, walked nodes and the reading. + if (![_composingBuffer length]) { + return NO; + } + _bpmfReadingBuffer->clear(); + _builder->clear(); + _walkedNodes.clear(); + [_composingBuffer setString:@""]; + } + else { + // if reading is not empty, we cancel the reading; Apple's built-in + // Zhuyin (and the erstwhile Hanin) has a default option that Esc + // "cancels" the current composed character and revert it to + // Bopomofo reading, in odds with the expectation of users from + // other platforms + + if (_bpmfReadingBuffer->isEmpty()) { + // no nee to beep since the event is deliberately triggered by user - if (escToClearInputBufferEnabled) { - // if the optioon is enabled, we clear everythiong including the composing - // buffer, walked nodes and the reading. if (![_composingBuffer length]) { return NO; } - _bpmfReadingBuffer->clear(); - _builder->clear(); - _walkedNodes.clear(); - [_composingBuffer setString:@""]; } else { - // if reading is not empty, we cancel the reading; Apple's built-in - // Zhuyin (and the erstwhile Hanin) has a default option that Esc - // "cancels" the current composed character and revert it to - // Bopomofo reading, in odds with the expectation of users from - // other platforms - - if (_bpmfReadingBuffer->isEmpty()) { - // no nee to beep since the event is deliberately triggered by user - - if (![_composingBuffer length]) { - return NO; - } - } - else { - _bpmfReadingBuffer->clear(); - } + _bpmfReadingBuffer->clear(); } + } - [self updateClientComposingBuffer:client]; + [self updateClientComposingBuffer:client]; return YES; } @@ -1161,33 +1065,16 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } + // if nothing is matched, see if it's a punctuation key for current layout. string layout = [self currentLayout]; string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); - if (_languageModel->hasUnigramsForKey(customPunctuation)) { - if (_bpmfReadingBuffer->isEmpty()) { - _builder->insertReadingAtCursor(customPunctuation); - [self popOverflowComposingTextAndWalk:client]; - } - else { // If there is still unfinished bpmf reading, ignore the punctuation - [self beep]; - } - [self updateClientComposingBuffer:client]; - + if ([self handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } - // if nothing is matched, see if it's a punctuation key + // if nothing is matched, see if it's a punctuation key. string punctuation = string("_punctuation_") + string(1, (char)charCode); - if (_languageModel->hasUnigramsForKey(punctuation)) { - if (_bpmfReadingBuffer->isEmpty()) { - _builder->insertReadingAtCursor(punctuation); - [self popOverflowComposingTextAndWalk:client]; - } - else { // If there is still unfinished bpmf reading, ignore the punctuation - [self beep]; - } - [self updateClientComposingBuffer:client]; - + if ([self handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } @@ -1203,16 +1090,48 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return NO; } -- (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode +- (BOOL)handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client { + if (_languageModel->hasUnigramsForKey(customPunctuation)) { + if (_bpmfReadingBuffer->isEmpty()) { + _builder->insertReadingAtCursor(customPunctuation); + [self popOverflowComposingTextAndWalk:client]; + } + else { // If there is still unfinished bpmf reading, ignore the punctuation + [self beep]; + } + [self updateClientComposingBuffer:client]; - BOOL cancelCandidateKey = (charCode == 27); + if (_inputMode == kSimpBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) { + [self collectCandidates]; + if ([_candidates count] == 1) { + [self commitComposition:client]; + } + else { + [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; + } + } + return YES; + } + return NO; +} +- (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode +{ + BOOL cancelCandidateKey = + (charCode == 27) || + ((_inputMode == kSimpBopomofoModeIdentifier) && + (charCode == 8 || keyCode == kDeleteKeyCode)); if (cancelCandidateKey) { gCurrentCandidateController.visible = NO; [_candidates removeAllObjects]; + if (_inputMode == kSimpBopomofoModeIdentifier) { + _builder->clear(); + _walkedNodes.clear(); + [_composingBuffer setString:@""]; + } [self updateClientComposingBuffer:_currentCandidateClient]; return YES; } @@ -1349,6 +1268,23 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } + if (_inputMode == kSimpBopomofoModeIdentifier) { + string layout = [self currentLayout]; + string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); + string punctuation = string("_punctuation_") + string(1, (char)charCode); + + BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || _languageModel->hasUnigramsForKey(customPunctuation) || + _languageModel->hasUnigramsForKey(punctuation); + + if (shouldAutoSelectCandidate) { + NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0]; + if (candidateIndex != NSUIntegerMax) { + [self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:candidateIndex]; + return [self handleInputText:inputText key:keyCode modifiers:0 client:_currentCandidateClient]; + } + } + } + [self beep]; [self updateClientComposingBuffer:_currentCandidateClient]; return YES; @@ -1491,8 +1427,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } NSString *klFontName = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeyLabelFontName]; NSString *ckeys = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeys]; - gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont monospacedDigitSystemFontOfSize:keyLabelSize weight:NSFontWeightMedium]; - gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize weight:NSFontWeightRegular]; + gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont systemFontOfSize:keyLabelSize]; + gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize]; NSMutableArray *keyLabels = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", nil]; @@ -1506,6 +1442,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } gCurrentCandidateController.keyLabels = keyLabels; [self collectCandidates]; + if (_inputMode == kSimpBopomofoModeIdentifier && [_candidates count] == 1) { + [self commitComposition:client]; + return; + } + gCurrentCandidateController.delegate = self; [gCurrentCandidateController reloadData]; @@ -1556,41 +1497,48 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES]; } -- (void)openUserPhrases:(id)sender +- (BOOL)_checkUserFiles { - NSLog(@"openUserPhrases called"); - if (!LTCheckIfUserLanguageModelFileExists()) { - NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()]; + if (![LanguageModelManager checkIfUserLanguageModelFilesExist] ) { + NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), [LanguageModelManager dataFolderPath]]; [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; - return; + return NO; } - if (_inputMode == kSimpBopomofoModeIdentifier) { - NSLog(@"editUserPhrases CHS called"); - NSString *path = LTUserPhrasesDataPathCHS(); - NSLog(@"Open %@", path); - if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { - [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES]; - } - NSURL *url = [NSURL fileURLWithPath:path]; - [[NSWorkspace sharedWorkspace] openURL:url]; - } else { - NSLog(@"editUserPhrases CHT called"); - NSString *path = LTUserPhrasesDataPathCHT(); - NSLog(@"Open %@", path); - if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { - [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES]; - } - NSURL *url = [NSURL fileURLWithPath:path]; - [[NSWorkspace sharedWorkspace] openURL:url]; + return YES; +} + +- (void)_openUserFile:(NSString *)path +{ + if (![self _checkUserFiles]) { + return; } + NSURL *url = [NSURL fileURLWithPath:path]; + [[NSWorkspace sharedWorkspace] openURL:url]; +} +- (void)openUserPhrases:(id)sender +{ + NSLog(@"openUserPhrases called"); + [self _openUserFile:[LanguageModelManager userPhrasesDataPathBopomofo]]; +} +- (void)openExcludedPhrasesSimpBopomofo:(id)sender +{ + NSLog(@"openExcludedPhrasesSimpBopomofo called"); + [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathSimpBopomofo]]; +} + +- (void)openExcludedPhrasesvChewing:(id)sender +{ + NSLog(@"openExcludedPhrasesvChewing called"); + [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathBopomofo]]; } - (void)reloadUserPhrases:(id)sender { - LTLoadUserLanguageModelFile(); + NSLog(@"reloadUserPhrases called"); + [LanguageModelManager loadUserPhrasesModel]; } - (void)showAbout:(id)sender @@ -1604,9 +1552,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { _chineseConversionEnabled = !_chineseConversionEnabled; [[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"ChineseConversionStatusChanged" object:nil]; } +@end + +#pragma mark - + +@implementation vChewingInputMethodController (VTCandidateController) + - (NSUInteger)candidateCountForController:(VTCandidateController *)controller { return [_candidates count]; @@ -1626,69 +1579,19 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } size_t cursorIndex = [self actualCandidateCursorIndex]; _builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue); - - _uom->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); + if (_inputMode != kSimpBopomofoModeIdentifier) { + _userOverrideModel->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); + } [_candidates removeAllObjects]; [self walk]; [self updateClientComposingBuffer:_currentCandidateClient]; -} - -- (void)handleChineseConversionStatusDidChanged:(NSNotification *)notification -{ - // Do not post the notification if status doesn't change. - // This is because the input method can be initiated by multiple applications, then all of them would post the notification. - if (_previousChineseConversionEnabledStatus == _chineseConversionEnabled) { + if (_inputMode == kSimpBopomofoModeIdentifier) { + [self commitComposition:_currentCandidateClient]; return; } - - NSUserNotification *userNotification = [[NSUserNotification alloc] init]; - userNotification.title = @"vChewing"; - userNotification.informativeText = [NSString stringWithFormat:@"%@%@", NSLocalizedString(@"Chinese Conversion", @""), _chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")]; - userNotification.soundName = NSUserNotificationDefaultSoundName; - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification]; - - _previousChineseConversionEnabledStatus = _chineseConversionEnabled; } @end - -static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm) -{ - NSString *dataPath = [[NSBundle bundleForClass:[vChewingInputMethodController class]] pathForResource:filenameWithoutExtension ofType:@"txt"]; - bool result = lm.open([dataPath UTF8String]); - if (!result) { - NSLog(@"Failed opening language model: %@", dataPath); - } -} - -void LTLoadLanguageModel() -{ - LTLoadLanguageModelFile(@"data", gLanguageModelCHT); - LTLoadLanguageModelFile(@"data-chs", gLanguageModelCHS); -} - -void LTLoadUserLanguageModelFile() -{ - // Autofix: Ensure that there's a new line in the user language model file. - // NSString *lineBreak = @"\n"; - // NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:LTUserPhrasesDataPathCHT() append:YES]; - // [stream open]; - // NSData *strData = [lineBreak dataUsingEncoding:NSUTF8StringEncoding]; - // [stream write:(uint8_t *)[strData bytes] maxLength:[strData length]]; - // [stream close]; - - gUserPhraseLanguageModelCHT.close(); - gUserPhraseLanguageModelCHS.close(); - bool resultCHT = gUserPhraseLanguageModelCHT.open([LTUserPhrasesDataPathCHT() UTF8String]); - bool resultCHS = gUserPhraseLanguageModelCHS.open([LTUserPhrasesDataPathCHS() UTF8String]); - - if (!resultCHT) { - NSLog(@"Failed opening language model for CHT user phrases."); - } - if (!resultCHS) { - NSLog(@"Failed opening language model for CHS user phrases."); - } -} diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h new file mode 100644 index 000000000..be4476a35 --- /dev/null +++ b/Source/LanguageModelManager.h @@ -0,0 +1,60 @@ +// +// LanguageModelManager.h +// +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#import +#import "FastLM.h" +#import "UserOverrideModel.h" +#import "vChewingLM.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface LanguageModelManager : NSObject + ++ (void)loadDataModels; ++ (void)loadUserPhrasesModel; ++ (BOOL)checkIfUserLanguageModelFilesExist; ++ (BOOL)writeUserPhrase:(NSString *)userPhrase; + +@property (class, readonly, nonatomic) NSString *dataFolderPath; +@property (class, readonly, nonatomic) NSString *userPhrasesDataPathBopomofo; +@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathBopomofo; +@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathSimpBopomofo; +@property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelBopomofo; +@property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelSimpBopomofo; +@property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModel; +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm new file mode 100644 index 000000000..a955bf97e --- /dev/null +++ b/Source/LanguageModelManager.mm @@ -0,0 +1,191 @@ +// +// LanguageModelManager.mm +// +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// Hiraku Wang (@hirakujira) @ vChewing +// Shiki Suen (@ShikiSuen) @ vChewing +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#import "LanguageModelManager.h" +#import +#import +#import +#import "OVStringHelper.h" +#import "OVUTF8Helper.h" + +using namespace std; +using namespace Formosa::Gramambular; +using namespace vChewing; +using namespace OpenVanilla; + +static const int kUserOverrideModelCapacity = 500; +static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. + +vChewingLM glanguageModelBopomofo; +vChewingLM gLanguageModelSimpBopomofo; +UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); + +@implementation LanguageModelManager + +static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewingLM &lm) +{ + Class cls = NSClassFromString(@"vChewingInputMethodController"); + NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"]; + lm.loadLanguageModel([dataPath UTF8String]); +} + ++ (void)loadDataModels +{ + LTLoadLanguageModelFile(@"data", glanguageModelBopomofo); + LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelSimpBopomofo); +} + ++ (void)loadUserPhrasesModel +{ + glanguageModelBopomofo.loadUserPhrases([[self userPhrasesDataPathBopomofo] UTF8String], [[self excludedPhrasesDataPathBopomofo] UTF8String]); + gLanguageModelSimpBopomofo.loadUserPhrases(NULL, [[self excludedPhrasesDataPathSimpBopomofo] UTF8String]); +} + ++ (BOOL)checkIfUserDataFolderExists +{ + NSString *folderPath = [self dataFolderPath]; + BOOL isFolder = NO; + BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; + if (folderExist && !isFolder) { + NSError *error = nil; + [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; + if (error) { + NSLog(@"Failed to remove folder %@", error); + return NO; + } + folderExist = NO; + } + if (!folderExist) { + NSError *error = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error]; + if (error) { + NSLog(@"Failed to create folder %@", error); + return NO; + } + } + return YES; +} + ++ (BOOL)checkIfFileExist:(NSString *)filePath +{ + if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES]; + if (!result) { + NSLog(@"Failed to write file"); + return NO; + } + } + return YES; +} + ++ (BOOL)checkIfUserLanguageModelFilesExist +{ + if (![self checkIfUserDataFolderExists]) { + return NO; + } + if (![self checkIfFileExist:[self userPhrasesDataPathBopomofo]]) { + return NO; + } + if (![self checkIfFileExist:[self excludedPhrasesDataPathBopomofo]]) { + return NO; + } + if (![self checkIfFileExist:[self excludedPhrasesDataPathSimpBopomofo]]) { + return NO; + } + return YES; +} + ++ (BOOL)writeUserPhrase:(NSString *)userPhrase +{ + if (![self checkIfUserLanguageModelFilesExist]) { + return NO; + } + + NSString *currentMarkedPhrase = [userPhrase stringByAppendingString:@"\n"]; + + NSString *path = [self userPhrasesDataPathBopomofo]; + NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; + if (!file) { + return NO; + } + [file seekToEndOfFile]; + NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; + [file writeData:data]; + [file closeFile]; + + [self loadUserPhrasesModel]; + return YES; +} + ++ (NSString *)dataFolderPath +{ + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES); + NSString *appSupportPath = [paths objectAtIndex:0]; + NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"]; + return userDictPath; +} + ++ (NSString *)userPhrasesDataPathBopomofo +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"data.txt"]; +} + ++ (NSString *)excludedPhrasesDataPathBopomofo +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases.txt"]; +} + ++ (NSString *)excludedPhrasesDataPathSimpBopomofo +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"]; +} + + + (vChewingLM *)languageModelBopomofo +{ + return &glanguageModelBopomofo; +} + ++ (vChewingLM *)languageModelSimpBopomofo +{ + return &gLanguageModelSimpBopomofo; +} + ++ (vChewing::UserOverrideModel *)userOverrideModel +{ + return &gUserOverrideModel; +} + +@end diff --git a/Source/Shit4Migration.txt b/Source/Shit4Migration.txt new file mode 100644 index 000000000..9d4fea3f0 --- /dev/null +++ b/Source/Shit4Migration.txt @@ -0,0 +1,71 @@ +// shared language model object that stores our phrase-term probability database +FastLM gLanguageModelCHT; +FastLM gLanguageModelCHS; +FastLM gUserPhraseLanguageModelCHT; +FastLM gUserPhraseLanguageModelCHS; + +static const int kUserOverrideModelCapacity = 500; +static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. +vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife); +vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife); + +static NSString *LTUserDataFolderPath() +{ + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES); + NSString *appSupportPath = [paths objectAtIndex:0]; + NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"]; + return userDictPath; +} + +static NSString *LTUserPhrasesDataPathCHT() +{ + return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-cht.txt"]; +} + +static NSString *LTUserPhrasesDataPathCHS() +{ + return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-chs.txt"]; +} + +static BOOL LTCheckIfUserLanguageModelFileExists() { + + NSString *folderPath = LTUserDataFolderPath(); + BOOL isFolder = NO; + BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; + if (folderExist && !isFolder) { + NSError *error = nil; + [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; + if (error) { + NSLog(@"Failed to remove folder %@", error); + return NO; + } + folderExist = NO; + } + if (!folderExist) { + NSError *error = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error]; + if (error) { + NSLog(@"Failed to create folder %@", error); + return NO; + } + } + NSString *filePathCHS = LTUserPhrasesDataPathCHS(); + if (![[NSFileManager defaultManager] fileExistsAtPath:filePathCHS]) { + BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePathCHS atomically:YES]; + if (!result) { + NSLog(@"Failed to write userdict CHS file"); + return NO; + } + } + NSString *filePathCHT = LTUserPhrasesDataPathCHT(); + if (![[NSFileManager defaultManager] fileExistsAtPath:filePathCHT]) { + BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePathCHT atomically:YES]; + if (!result) { + NSLog(@"Failed to write userdict CHT file"); + return NO; + } + } + return YES; +} + + diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index a82c6261e..8678d2740 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -23,3 +23,4 @@ "Reload User Phrases" = "Reload User Phrases"; "Unable to create the user phrase file." = "Unable to create the user phrase file."; "Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\"."; +"Edit Excluded Phrases" = "Edit Excluded Phrases"; diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index 5ba232f60..95c5f4f2f 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -3,5 +3,9 @@ // #import "frmAboutWindow.h" -extern void LTLoadLanguageModel(void); -extern void LTLoadUserLanguageModelFile(void); +#import // @import Foundation; +@interface LanguageModelManager : NSObject ++ (void)loadDataModels; ++ (void)loadUserPhrasesModel; ++ (BOOL)checkIfUserLanguageModelFilesExist; +@end diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index d3f45f5a2..6f5d95666 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -23,3 +23,4 @@ "Reload User Phrases" = "重载自订语汇"; "Unable to create the user phrase file." = "无法创建自订语汇档案。"; "Please check the permission of at \"%@\"." = "请检查此处的存取权限:\"%@\"."; +"Edit Excluded Phrases" = "编辑要滤除的语汇"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index a36c50b87..719a3757a 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -23,3 +23,4 @@ "Reload User Phrases" = "重載自訂語彙"; "Unable to create the user phrase file." = "無法創建自訂語彙檔案。"; "Please check the permission of at \"%@\"." = "請檢查此處的存取權限:\"%@\"."; +"Edit Excluded Phrases" = "編輯要濾除的語彙"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 7910062dc..bb2e0901a 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 5B1958522788A2BF00FAEB14 /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */; }; 5B58E87F278413E7003EA2AD /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; + 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */; }; + 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */; }; 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */; }; 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; @@ -83,6 +85,11 @@ 5B58E87E278413E7003EA2AD /* en */ = {isa = PBXFileReference; lastKnownFileType = text; name = en; path = Source/en.lproj/MITLicense.txt; sourceTree = SOURCE_ROOT; }; 5B58E880278413EF003EA2AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hans"; path = "zh-Hans.lproj/MITLicense.txt"; sourceTree = ""; }; 5B58E881278413F1003EA2AD /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/MITLicense.txt"; sourceTree = ""; }; + 5B5F4F8C27928F9300922DC2 /* vChewingLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vChewingLM.h; sourceTree = ""; }; + 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = vChewingLM.cpp; sourceTree = ""; }; + 5B5F4F91279294A300922DC2 /* LanguageModelManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LanguageModelManager.h; sourceTree = ""; }; + 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = ""; }; + 5B5F4F9427929ADC00922DC2 /* Shit4Migration.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Shit4Migration.txt; sourceTree = ""; }; 5B9781D32763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -229,6 +236,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5BA8DAFE27928120009C9FFF /* LanguageModel */ = { + isa = PBXGroup; + children = ( + 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */, + 5B5F4F8C27928F9300922DC2 /* vChewingLM.h */, + 6A0421A615FEF3F50061ED63 /* FastLM.cpp */, + 6A0421A715FEF3F50061ED63 /* FastLM.h */, + 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */, + 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */, + ); + path = LanguageModel; + sourceTree = ""; + }; 6A0D4E9215FC0CFA00ABF4B3 = { isa = PBXGroup; children = ( @@ -272,6 +292,9 @@ 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */, 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */, 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, + 5B5F4F9427929ADC00922DC2 /* Shit4Migration.txt */, + 5B5F4F91279294A300922DC2 /* LanguageModelManager.h */, + 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */, 6A0D4EC815FC0D6400ABF4B3 /* main.m */, 6A0D4EF615FC0DA600ABF4B3 /* vChewing-Prefix.pch */, 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */, @@ -280,8 +303,6 @@ 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */, D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */, 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */, - 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */, - 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */, ); path = Source; sourceTree = ""; @@ -311,11 +332,10 @@ 6A0D4F1215FC0EB100ABF4B3 /* Engine */ = { isa = PBXGroup; children = ( + 5BA8DAFE27928120009C9FFF /* LanguageModel */, 6A0D4F1315FC0EB100ABF4B3 /* Gramambular */, 6A0D4F1F15FC0EB100ABF4B3 /* Mandarin */, 6A0D4F2215FC0EB100ABF4B3 /* OpenVanilla */, - 6A0421A615FEF3F50061ED63 /* FastLM.cpp */, - 6A0421A715FEF3F50061ED63 /* FastLM.h */, ); path = Engine; sourceTree = ""; @@ -618,9 +638,11 @@ buildActionMask = 2147483647; files = ( 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */, + 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, + 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */, 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, -- Gitee From 650d08f5e231541f624ca045242b6d979da5746e Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 14:56:53 +0800 Subject: [PATCH 046/163] Zonble: mgrLanguageModel // Adds UserPhrasesLM for user phrases. --- Source/Engine/LanguageModel/UserPhrasesLM.cpp | 236 ++++++++++++++++++ Source/Engine/LanguageModel/UserPhrasesLM.h | 81 ++++++ Source/Engine/LanguageModel/vChewingLM.h | 5 +- Source/InputMethodController.mm | 2 - Source/LanguageModelManager.mm | 39 ++- Source/Shit4Migration.txt | 71 ------ Source/vChewing-Bridging-Header.h | 1 - vChewing.xcodeproj/project.pbxproj | 8 +- 8 files changed, 358 insertions(+), 85 deletions(-) create mode 100644 Source/Engine/LanguageModel/UserPhrasesLM.cpp create mode 100644 Source/Engine/LanguageModel/UserPhrasesLM.h delete mode 100644 Source/Shit4Migration.txt diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.cpp b/Source/Engine/LanguageModel/UserPhrasesLM.cpp new file mode 100644 index 000000000..7c88f9e97 --- /dev/null +++ b/Source/Engine/LanguageModel/UserPhrasesLM.cpp @@ -0,0 +1,236 @@ +// +// UserPhraseLM.cpp +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#include "UserPhrasesLM.h" +#include +#include +#include +#include +#include + +using namespace Formosa::Gramambular; +using namespace vChewing; + +UserPhrasesLM::UserPhrasesLM() + : fd(-1) + , data(0) + , length(0) +{ +} + +UserPhrasesLM::~UserPhrasesLM() +{ + if (data) { + close(); + } +} + +bool UserPhrasesLM::open(const char *path) +{ + if (data) { + return false; + } + + fd = ::open(path, O_RDONLY); + if (fd == -1) { + printf("open:: file not exist"); + return false; + } + + struct stat sb; + if (fstat(fd, &sb) == -1) { + printf("open:: cannot open file"); + return false; + } + + length = (size_t)sb.st_size; + + data = mmap(NULL, length, PROT_WRITE, MAP_PRIVATE, fd, 0); + if (!data) { + ::close(fd); + return false; + } + + char *head = (char *)data; + char *end = (char *)data + length; + char c; + Row row; + +start: + // EOF -> end + if (head == end) { + goto end; + } + + c = *head; + // \s -> error + if (c == ' ') { + goto error; + } + // \n -> start + else if (c == '\n') { + head++; + goto start; + } + + // \w -> record column star, state1 + row.value = head; + head++; + // fall through to state 1 + +state1: + // EOF -> error + if (head == end) { + goto error; + } + + c = *head; + // \n -> error + if (c == '\n') { + goto error; + } + // \s -> state2 + zero out ending + record column start + else if (c == ' ') { + *head = 0; + head++; + row.key = head; + goto state2; + } + + // \w -> state1 + head++; + goto state1; + +state2: + if (head == end) { + *head = 0; + head++; + keyRowMap[row.key].push_back(row); + goto end; + } + + c = *head; + // \s -> error + if (c == ' ' || c == '\n') { + *head = 0; + head++; + keyRowMap[row.key].push_back(row); + if (c == ' ') { + goto state3; + } + goto start; + } + + // \w -> state 2 + head++; + goto state2; + +state3: + if (head == end) { + *head = 0; + head++; + keyRowMap[row.key].push_back(row); + goto end; + } + + c = *head; + if (c == '\n') { + goto start; + } + + head++; + goto state3; + +error: + close(); + return false; + +end: + static const char *space = " "; + Row emptyRow; + emptyRow.key = space; + emptyRow.value = space; + keyRowMap[space].push_back(emptyRow); + + return true; +} + +void UserPhrasesLM::close() +{ + if (data) { + munmap(data, length); + ::close(fd); + data = 0; + } + + keyRowMap.clear(); +} + +void UserPhrasesLM::dump() +{ + size_t rows = 0; + for (map >::const_iterator i = keyRowMap.begin(), e = keyRowMap.end(); i != e; ++i) { + const vector& r = (*i).second; + for (vector::const_iterator ri = r.begin(), re = r.end(); ri != re; ++ri) { + const Row& row = *ri; + cerr << row.key << " " << row.value << "\n"; + rows++; + } + } +} + +const vector UserPhrasesLM::bigramsForKeys(const string& preceedingKey, const string& key) +{ + return vector(); +} + +const vector UserPhrasesLM::unigramsForKey(const string& key) +{ + vector v; + map >::const_iterator i = keyRowMap.find(key.c_str()); + + if (i != keyRowMap.end()) { + for (vector::const_iterator ri = (*i).second.begin(), re = (*i).second.end(); ri != re; ++ri) { + Unigram g; + const Row& r = *ri; + g.keyValue.key = r.key; + g.keyValue.value = r.value; + g.score = 0.0; + v.push_back(g); + } + } + + return v; +} + +bool UserPhrasesLM::hasUnigramsForKey(const string& key) +{ + return keyRowMap.find(key.c_str()) != keyRowMap.end(); +} diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.h b/Source/Engine/LanguageModel/UserPhrasesLM.h new file mode 100644 index 000000000..2ff27a308 --- /dev/null +++ b/Source/Engine/LanguageModel/UserPhrasesLM.h @@ -0,0 +1,81 @@ +// +// UserPhraseLM.h +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#ifndef USERPHRASESLM_H +#define USERPHRASESLM_H + +#include + +#include +#include +#include +#include "LanguageModel.h" + +namespace vChewing { + +using namespace Formosa::Gramambular; + +class UserPhrasesLM : public LanguageModel +{ +public: + UserPhrasesLM(); + ~UserPhrasesLM(); + + bool open(const char *path); + void close(); + void dump(); + + virtual const vector bigramsForKeys(const string& preceedingKey, const string& key); + virtual const vector unigramsForKey(const string& key); + virtual bool hasUnigramsForKey(const string& key); + +protected: + struct CStringCmp + { + bool operator()(const char* s1, const char* s2) const + { + return strcmp(s1, s2) < 0; + } + }; + + struct Row { + const char *key; + const char *value; + }; + + map, CStringCmp> keyRowMap; + int fd; + void *data; + size_t length; +}; + +} + +#endif diff --git a/Source/Engine/LanguageModel/vChewingLM.h b/Source/Engine/LanguageModel/vChewingLM.h index 29fb1587b..0d57fcea6 100644 --- a/Source/Engine/LanguageModel/vChewingLM.h +++ b/Source/Engine/LanguageModel/vChewingLM.h @@ -39,6 +39,7 @@ #include #include "FastLM.h" +#include "UserPhrasesLM.h" namespace vChewing { @@ -59,8 +60,8 @@ public: protected: FastLM m_languageModel; - FastLM m_userPhrases; - FastLM m_excludedPhrases; + UserPhrasesLM m_userPhrases; + UserPhrasesLM m_excludedPhrases; }; }; diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index c07c518de..efb15c5b5 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -618,8 +618,6 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; } [string appendString:[readingsArray componentsJoinedByString:@"-"]]; - [string appendString:@" "]; - [string appendString:@"-1.0"]; return string; } diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index a955bf97e..9ceed6c16 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -134,17 +134,42 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return NO; } - NSString *currentMarkedPhrase = [userPhrase stringByAppendingString:@"\n"]; - + BOOL shuoldAddLineBreakAtFront = NO; NSString *path = [self userPhrasesDataPathBopomofo]; - NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path]; - if (!file) { + + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + NSError *error = nil; + NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; + unsigned long long fileSize = [attr fileSize]; + if (!error && fileSize) { + NSFileHandle *readFile = [NSFileHandle fileHandleForReadingAtPath:path]; + if (readFile) { + [readFile seekToFileOffset:fileSize - 1]; + NSData *data = [readFile readDataToEndOfFile]; + const void *bytes = [data bytes]; + if (*(char *)bytes != '\n') { + shuoldAddLineBreakAtFront = YES; + } + [readFile closeFile]; + } + } + } + + NSMutableString *currentMarkedPhrase = [NSMutableString string]; + if (shuoldAddLineBreakAtFront) { + [currentMarkedPhrase appendString:@"\n"]; + } + [currentMarkedPhrase appendString:userPhrase]; + [currentMarkedPhrase appendString:@"\n"]; + + NSFileHandle *writeFile = [NSFileHandle fileHandleForUpdatingAtPath:path]; + if (!writeFile) { return NO; } - [file seekToEndOfFile]; + [writeFile seekToEndOfFile]; NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; - [file writeData:data]; - [file closeFile]; + [writeFile writeData:data]; + [writeFile closeFile]; [self loadUserPhrasesModel]; return YES; diff --git a/Source/Shit4Migration.txt b/Source/Shit4Migration.txt deleted file mode 100644 index 9d4fea3f0..000000000 --- a/Source/Shit4Migration.txt +++ /dev/null @@ -1,71 +0,0 @@ -// shared language model object that stores our phrase-term probability database -FastLM gLanguageModelCHT; -FastLM gLanguageModelCHS; -FastLM gUserPhraseLanguageModelCHT; -FastLM gUserPhraseLanguageModelCHS; - -static const int kUserOverrideModelCapacity = 500; -static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. -vChewing::UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife); -vChewing::UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife); - -static NSString *LTUserDataFolderPath() -{ - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES); - NSString *appSupportPath = [paths objectAtIndex:0]; - NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"]; - return userDictPath; -} - -static NSString *LTUserPhrasesDataPathCHT() -{ - return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-cht.txt"]; -} - -static NSString *LTUserPhrasesDataPathCHS() -{ - return [LTUserDataFolderPath() stringByAppendingPathComponent:@"userdata-chs.txt"]; -} - -static BOOL LTCheckIfUserLanguageModelFileExists() { - - NSString *folderPath = LTUserDataFolderPath(); - BOOL isFolder = NO; - BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder]; - if (folderExist && !isFolder) { - NSError *error = nil; - [[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error]; - if (error) { - NSLog(@"Failed to remove folder %@", error); - return NO; - } - folderExist = NO; - } - if (!folderExist) { - NSError *error = nil; - [[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error]; - if (error) { - NSLog(@"Failed to create folder %@", error); - return NO; - } - } - NSString *filePathCHS = LTUserPhrasesDataPathCHS(); - if (![[NSFileManager defaultManager] fileExistsAtPath:filePathCHS]) { - BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePathCHS atomically:YES]; - if (!result) { - NSLog(@"Failed to write userdict CHS file"); - return NO; - } - } - NSString *filePathCHT = LTUserPhrasesDataPathCHT(); - if (![[NSFileManager defaultManager] fileExistsAtPath:filePathCHT]) { - BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePathCHT atomically:YES]; - if (!result) { - NSLog(@"Failed to write userdict CHT file"); - return NO; - } - } - return YES; -} - - diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index 95c5f4f2f..e4c62d3f5 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -7,5 +7,4 @@ @interface LanguageModelManager : NSObject + (void)loadDataModels; + (void)loadUserPhrasesModel; -+ (BOOL)checkIfUserLanguageModelFilesExist; @end diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index bb2e0901a..e1d75a297 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 5B58E87F278413E7003EA2AD /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */; }; 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */; }; + 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */; }; 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */; }; 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; @@ -89,7 +90,8 @@ 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = vChewingLM.cpp; sourceTree = ""; }; 5B5F4F91279294A300922DC2 /* LanguageModelManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LanguageModelManager.h; sourceTree = ""; }; 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = ""; }; - 5B5F4F9427929ADC00922DC2 /* Shit4Migration.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Shit4Migration.txt; sourceTree = ""; }; + 5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPhrasesLM.h; sourceTree = ""; }; + 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UserPhrasesLM.cpp; sourceTree = ""; }; 5B9781D32763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -245,6 +247,8 @@ 6A0421A715FEF3F50061ED63 /* FastLM.h */, 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */, 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */, + 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */, + 5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */, ); path = LanguageModel; sourceTree = ""; @@ -292,7 +296,6 @@ 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */, 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */, 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, - 5B5F4F9427929ADC00922DC2 /* Shit4Migration.txt */, 5B5F4F91279294A300922DC2 /* LanguageModelManager.h */, 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */, 6A0D4EC815FC0D6400ABF4B3 /* main.m */, @@ -642,6 +645,7 @@ 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, + 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */, 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */, 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, -- Gitee From 4bbccca283a4098a9270de6550258543ffaf3092 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 17:27:50 +0800 Subject: [PATCH 047/163] Zonble: Input Experience // Half-Size Punctuation Support --- Source/InputMethodController.h | 3 +++ Source/InputMethodController.mm | 29 ++++++++++++++++++------ Source/en.lproj/Localizable.strings | 1 + Source/zh-Hans.lproj/Localizable.strings | 1 + Source/zh-Hant.lproj/Localizable.strings | 1 + 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index 7fc13239f..4649650b3 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -83,5 +83,8 @@ // if Chinese conversion is enabled BOOL _chineseConversionEnabled; + + // if half-width punctuation is enabled + BOOL _halfWidthPunctuationEnabled; } @end diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index efb15c5b5..2ce71eae5 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -81,6 +81,7 @@ static NSString *const kUseHorizontalCandidateListPreferenceKey = @"UseHorizonta static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize"; static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey"; static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey"; +static NSString *const kHalfWidthPunctuationEnabledKey = @"HalfWidthPunctuationEnabledKey"; static NSString *const kEscToCleanInputBufferKey = @"EscToCleanInputBufferKey"; // advanced (usually optional) settings @@ -192,6 +193,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) _inputMode = kBopomofoModeIdentifier; _chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; + _halfWidthPunctuationEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kHalfWidthPunctuationEnabledKey]; } return self; @@ -208,9 +210,12 @@ static double FindHighestScore(const vector& nodes, double epsilon) chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; [menu addItem:chineseConversionMenuItem]; + + NSMenuItem *halfWidthPunctuationMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""]; + halfWidthPunctuationMenuItem.state = _halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; + [menu addItem:halfWidthPunctuationMenuItem]; - [menu addItem:[NSMenuItem separatorItem]]; - [menu addItemWithTitle:NSLocalizedString(@"User Phrases", @"") action:NULL keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ if (_inputMode == kSimpBopomofoModeIdentifier) { NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesSimpBopomofo:) keyEquivalent:@""]; @@ -226,7 +231,8 @@ static double FindHighestScore(const vector& nodes, double epsilon) NSMenuItem *reloadUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; [menu addItem:reloadUserPhrasesItem]; - [menu addItem:[NSMenuItem separatorItem]]; + + [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; [menu addItem:updateCheckItem]; @@ -385,7 +391,8 @@ static double FindHighestScore(const vector& nodes, double epsilon) // Chinese conversion. NSString *buffer = _composingBuffer; - if (_chineseConversionEnabled) { + BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; + if (chineseConversionEnabled) { buffer = [OpenCCBridge convert:_composingBuffer]; } @@ -545,7 +552,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } NodeAnchor &anchor = _walkedNodes[0]; NSString *popedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()]; // Chinese conversion. - if (_chineseConversionEnabled) { + BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; + if (chineseConversionEnabled) { popedText = [OpenCCBridge convert:popedText]; } [client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; @@ -1065,13 +1073,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // if nothing is matched, see if it's a punctuation key for current layout. string layout = [self currentLayout]; - string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); + string punctuationNamePrefix = (_halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_")); + string customPunctuation = punctuationNamePrefix + layout + string(1, (char)charCode); if ([self handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } // if nothing is matched, see if it's a punctuation key. - string punctuation = string("_punctuation_") + string(1, (char)charCode); + string punctuation = punctuationNamePrefix + string(1, (char)charCode); if ([self handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } @@ -1552,6 +1561,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey]; } +- (void)toggleHalfWidthPunctuation:(id)sender +{ + _halfWidthPunctuationEnabled = !_halfWidthPunctuationEnabled; + [[NSUserDefaults standardUserDefaults] setBool:_halfWidthPunctuationEnabled forKey:kHalfWidthPunctuationEnabledKey]; +} + @end #pragma mark - diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 8678d2740..8d205a43d 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -24,3 +24,4 @@ "Unable to create the user phrase file." = "Unable to create the user phrase file."; "Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\"."; "Edit Excluded Phrases" = "Edit Excluded Phrases"; +"Use Half-Width Punctuations" = "Use Half-Width Punctuations"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 6f5d95666..15ef591e0 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -24,3 +24,4 @@ "Unable to create the user phrase file." = "无法创建自订语汇档案。"; "Please check the permission of at \"%@\"." = "请检查此处的存取权限:\"%@\"."; "Edit Excluded Phrases" = "编辑要滤除的语汇"; +"Use Half-Width Punctuations" = "啟用半角標點輸出"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 719a3757a..385f29311 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -24,3 +24,4 @@ "Unable to create the user phrase file." = "無法創建自訂語彙檔案。"; "Please check the permission of at \"%@\"." = "請檢查此處的存取權限:\"%@\"."; "Edit Excluded Phrases" = "編輯要濾除的語彙"; +"Use Half-Width Punctuations" = "啟用半形標點輸出"; -- Gitee From 3b5f24f328320952d0d64f14456a0e948ea596df Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 18:47:36 +0800 Subject: [PATCH 048/163] Zonble: Input Experience // Emacs-style hotkeys --- Source/InputMethodController.mm | 100 +++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 21 deletions(-) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 2ce71eae5..feafcd6fc 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -107,6 +107,16 @@ enum { kDeleteKeyCode = 117 }; +typedef NS_ENUM(NSUInteger, vChewingEmacsKey) { + vChewingEmacsKeyNone, + vChewingEmacsKeyForward, + vChewingEmacsKeyBackward, + vChewingEmacsKeyHome, + vChewingEmacsKeyEnd, + vChewingEmacsKeyDelete, + vChewingEmacsKeyNextPage, +}; + VTCandidateController *gCurrentCandidateController = nil; // if DEBUG is defined, a DOT file (GraphViz format) will be written to the @@ -570,7 +580,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } NSBeep(); } -- (string)currentLayout +- (string)_currentLayout { string layout = string("Standard_");; NSInteger keyboardLayout = [[NSUserDefaults standardUserDefaults] integerForKey:kKeyboardLayoutPreferenceKey]; @@ -639,6 +649,32 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return [LanguageModelManager writeUserPhrase:currentMarkedPhrase]; } +- (vChewingEmacsKey)_detectEmacsKeyFromCharCode:(UniChar)charCode modifiers:(NSUInteger)flags +{ + if (flags & NSControlKeyMask) { + char c = charCode + 'a' - 1; + if (c == 'a') { + return vChewingEmacsKeyHome; + } + else if (c == 'e') { + return vChewingEmacsKeyEnd; + } + else if (c == 'f') { + return vChewingEmacsKeyForward; + } + else if (c == 'b') { + return vChewingEmacsKeyBackward; + } + else if (c == 'd') { + return vChewingEmacsKeyDelete; + } + else if (c == 'v') { + return vChewingEmacsKeyNextPage; + } + } + return vChewingEmacsKeyNone; +} + - (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client { NSRect textFrame = NSZeroRect; @@ -663,6 +699,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // get the unicode character code UniChar charCode = [inputText length] ? [inputText characterAtIndex:0] : 0; + + vChewingEmacsKey emacsKey = [self _detectEmacsKeyFromCharCode:charCode modifiers:flags]; if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) { // special handling for com.apple.Terminal @@ -675,7 +713,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it - if (![_composingBuffer length] && _bpmfReadingBuffer->isEmpty() && ((flags & NSCommandKeyMask) || (flags & NSControlKeyMask) || (flags & NSAlternateKeyMask) || (flags & NSNumericPadKeyMask))) { + if (![_composingBuffer length] && + _bpmfReadingBuffer->isEmpty() && + ((flags & NSCommandKeyMask) || (flags & NSControlKeyMask) || (flags & NSAlternateKeyMask) || (flags & NSNumericPadKeyMask))) { return NO; } @@ -719,7 +759,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // if we have candidate, it means we need to pass the event to the candidate handler if ([_candidates count]) { - return [self handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode]; + return [self _handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode emacsKey:(vChewingEmacsKey)emacsKey]; } // If we have marker index. @@ -741,8 +781,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - // Shift + left - if (keyCode == cursorBackwardKey && (flags & NSShiftKeyMask)) { + // Shift + Left // Shift + Up in vertical tyinging mode + if ((keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) + && (flags & NSShiftKeyMask)) { if (_builder->markerCursorIndex() > 0) { _builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1); } @@ -752,8 +793,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - // Shift + Right - if (keyCode == cursorForwardKey && (flags & NSShiftKeyMask)) { + // Shift + Right // Shift + Down in vertical tyinging mode + if ((keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) + && (flags & NSShiftKeyMask)) { if (_builder->markerCursorIndex() < _builder->length()) { _builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1); } @@ -886,7 +928,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // handle cursor backward - if (keyCode == cursorBackwardKey) { + if (keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; } @@ -918,7 +960,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // handle cursor forward - if (keyCode == cursorForwardKey) { + if (keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; } @@ -948,7 +990,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } - if (keyCode == kHomeKeyCode) { + if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; } @@ -969,7 +1011,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } - if (keyCode == kEndKeyCode) { + if (keyCode == kEndKeyCode || emacsKey == vChewingEmacsKeyEnd) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; } @@ -1022,7 +1064,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // Delete - if (keyCode == kDeleteKeyCode) { + if (keyCode == kDeleteKeyCode || emacsKey == vChewingEmacsKeyDelete) { if (_bpmfReadingBuffer->isEmpty()) { if (![_composingBuffer length]) { return NO; @@ -1072,16 +1114,16 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // if nothing is matched, see if it's a punctuation key for current layout. - string layout = [self currentLayout]; + string layout = [self _currentLayout]; string punctuationNamePrefix = (_halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_")); string customPunctuation = punctuationNamePrefix + layout + string(1, (char)charCode); - if ([self handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) { + if ([self _handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } // if nothing is matched, see if it's a punctuation key. string punctuation = punctuationNamePrefix + string(1, (char)charCode); - if ([self handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) { + if ([self _handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } @@ -1097,7 +1139,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return NO; } -- (BOOL)handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client +- (BOOL)_handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client { if (_languageModel->hasUnigramsForKey(customPunctuation)) { if (_bpmfReadingBuffer->isEmpty()) { @@ -1123,7 +1165,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return NO; } -- (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode +- (BOOL)_handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode emacsKey:(vChewingEmacsKey)emacsKey { BOOL cancelCandidateKey = (charCode == 27) || @@ -1146,7 +1188,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:gCurrentCandidateController.selectedCandidateIndex]; return YES; } - else if (charCode == 32 || keyCode == kPageDownKeyCode) { + else if (charCode == 32 || keyCode == kPageDownKeyCode || emacsKey == vChewingEmacsKeyNextPage) { BOOL updated = [gCurrentCandidateController showNextPage]; if (!updated) { [self beep]; @@ -1180,6 +1222,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } } + else if (emacsKey == vChewingEmacsKeyBackward) { + BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; + if (!updated) { + [self beep]; + } + [self updateClientComposingBuffer:_currentCandidateClient]; + return YES; + } else if (keyCode == kRightKeyCode) { if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { BOOL updated = [gCurrentCandidateController highlightNextCandidate]; @@ -1198,6 +1248,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } } + else if (emacsKey == vChewingEmacsKeyForward) { + BOOL updated = [gCurrentCandidateController highlightNextCandidate]; + if (!updated) { + [self beep]; + } + [self updateClientComposingBuffer:_currentCandidateClient]; + return YES; + } else if (keyCode == kUpKeyCode) { if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { BOOL updated = [gCurrentCandidateController showPreviousPage]; @@ -1234,7 +1292,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } } - else if (keyCode == kHomeKeyCode) { + else if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) { if (gCurrentCandidateController.selectedCandidateIndex == 0) { [self beep]; @@ -1246,7 +1304,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:_currentCandidateClient]; return YES; } - else if (keyCode == kEndKeyCode && [_candidates count] > 0) { + else if ((keyCode == kEndKeyCode || emacsKey == vChewingEmacsKeyEnd) && [_candidates count] > 0) { if (gCurrentCandidateController.selectedCandidateIndex == [_candidates count] - 1) { [self beep]; } @@ -1276,7 +1334,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } if (_inputMode == kSimpBopomofoModeIdentifier) { - string layout = [self currentLayout]; + string layout = [self _currentLayout]; string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); string punctuation = string("_punctuation_") + string(1, (char)charCode); -- Gitee From ea1f6d9a12edfe49b068b4f5e301c2d41bbfbb11 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 15 Jan 2022 23:59:01 +0800 Subject: [PATCH 049/163] Zonble: In-Place User Phrase Guiding Tooltip --- Source/InputMethodController.mm | 505 ++++++++++-------- .../HorizontalCandidateController.swift | 0 .../CandidateUI/VTCandidateController.swift | 0 .../VerticalCandidateController.swift | 0 Source/UI/TooltipUI/TooltipController.swift | 100 ++++ Source/en.lproj/Localizable.strings | 2 + Source/zh-Hans.lproj/Localizable.strings | 2 + Source/zh-Hant.lproj/Localizable.strings | 2 + vChewing.xcodeproj/project.pbxproj | 22 +- 9 files changed, 419 insertions(+), 214 deletions(-) rename Source/{ => UI}/CandidateUI/HorizontalCandidateController.swift (100%) rename Source/{ => UI}/CandidateUI/VTCandidateController.swift (100%) rename Source/{ => UI}/CandidateUI/VerticalCandidateController.swift (100%) create mode 100644 Source/UI/TooltipUI/TooltipController.swift diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index feafcd6fc..77b57878c 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -169,7 +169,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) if (_bpmfReadingBuffer) { delete _bpmfReadingBuffer; } - + if (_builder) { delete _builder; } @@ -181,31 +181,31 @@ static double FindHighestScore(const vector& nodes, double epsilon) { // an instance is initialized whenever a text input client (a Mac app) requires // text input from an IME - + self = [super initWithServer:server delegate:delegate client:client]; if (self) { _candidates = [[NSMutableArray alloc] init]; - + // create the reading buffer _bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout()); - + // create the lattice builder _languageModel = [LanguageModelManager languageModelBopomofo]; _userOverrideModel = [LanguageModelManager userOverrideModel]; - + _builder = new BlockReadingBuilder(_languageModel); - + // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); - + // create the composing buffer _composingBuffer = [[NSMutableString alloc] init]; - + _inputMode = kBopomofoModeIdentifier; _chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; _halfWidthPunctuationEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kHalfWidthPunctuationEnabledKey]; } - + return self; } @@ -215,18 +215,18 @@ static double FindHighestScore(const vector& nodes, double epsilon) NSMenu *menu = [[NSMenu alloc] initWithTitle:LocalizationNotNeeded(@"Input Method Menu")]; NSMenuItem *preferenceMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; [menu addItem:preferenceMenuItem]; - + NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; [menu addItem:chineseConversionMenuItem]; NSMenuItem *halfWidthPunctuationMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""]; - halfWidthPunctuationMenuItem.state = _halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; - [menu addItem:halfWidthPunctuationMenuItem]; - + halfWidthPunctuationMenuItem.state = _halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; + [menu addItem:halfWidthPunctuationMenuItem]; + [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ - + if (_inputMode == kSimpBopomofoModeIdentifier) { NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesSimpBopomofo:) keyEquivalent:@""]; [menu addItem:editExcludedPhrasesItem]; @@ -234,19 +234,19 @@ static double FindHighestScore(const vector& nodes, double epsilon) else { NSMenuItem *editUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; [menu addItem:editUserPhrasesItem]; - + NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesvChewing:) keyEquivalent:@""]; [menu addItem:editExcludedPhrasesItem]; } - + NSMenuItem *reloadUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; [menu addItem:reloadUserPhrasesItem]; - + [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ - + NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; [menu addItem:updateCheckItem]; - + NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""]; [menu addItem:aboutMenuItem]; return menu; @@ -257,21 +257,21 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)activateServer:(id)client { [[NSUserDefaults standardUserDefaults] synchronize]; - + // Override the keyboard layout. Use US if not set. NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; if (!basisKeyboardLayoutID) { basisKeyboardLayoutID = @"com.apple.keylayout.US"; } [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - + // reset the state _currentDeferredClient = nil; _currentCandidateClient = nil; _builder->clear(); _walkedNodes.clear(); [_composingBuffer setString:@""]; - + // checks and populates the default settings NSInteger keyboardLayout = [[NSUserDefaults standardUserDefaults] integerForKey:kKeyboardLayoutPreferenceKey]; switch (keyboardLayout) { @@ -297,7 +297,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::StandardLayout()); [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:kKeyboardLayoutPreferenceKey]; } - + // set the size NSInteger textSize = [[NSUserDefaults standardUserDefaults] integerForKey:kCandidateListTextSizeKey]; NSInteger previousTextSize = textSize; @@ -310,14 +310,14 @@ static double FindHighestScore(const vector& nodes, double epsilon) else if (textSize > kMaxCandidateListTextSize) { textSize = kMaxCandidateListTextSize; } - + if (textSize != previousTextSize) { [[NSUserDefaults standardUserDefaults] setInteger:textSize forKey:kCandidateListTextSizeKey]; } if (![[NSUserDefaults standardUserDefaults] objectForKey:kChooseCandidateUsingSpaceKey]) { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kChooseCandidateUsingSpaceKey]; } - + [(AppDelegate *)[NSApp delegate] checkForUpdate]; } @@ -328,23 +328,25 @@ static double FindHighestScore(const vector& nodes, double epsilon) _bpmfReadingBuffer->clear(); [client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; } - + // commit any residue in the composing buffer [self commitComposition:client]; - + _currentDeferredClient = nil; _currentCandidateClient = nil; - + gCurrentCandidateController.delegate = nil; gCurrentCandidateController.visible = NO; [_candidates removeAllObjects]; + + [self _hideTooltip]; } - (void)setValue:(id)value forTag:(long)tag client:(id)sender { NSString *newInputMode; vChewingLM *newLanguageModel; - + if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) { newInputMode = kSimpBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelSimpBopomofo]; @@ -353,30 +355,30 @@ static double FindHighestScore(const vector& nodes, double epsilon) newInputMode = kBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelBopomofo]; } - + // Only apply the changes if the value is changed if (![_inputMode isEqualToString:newInputMode]) { [[NSUserDefaults standardUserDefaults] synchronize]; - + // Remember to override the keyboard layout again -- treat this as an activate eventy NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; if (!basisKeyboardLayoutID) { basisKeyboardLayoutID = @"com.apple.keylayout.US"; } [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - + _inputMode = newInputMode; _languageModel = newLanguageModel; - + if (!_bpmfReadingBuffer->isEmpty()) { _bpmfReadingBuffer->clear(); [self updateClientComposingBuffer:sender]; } - + if ([_composingBuffer length] > 0) { [self commitComposition:sender]; } - + if (_builder) { delete _builder; _builder = new BlockReadingBuilder(_languageModel); @@ -398,14 +400,14 @@ static double FindHighestScore(const vector& nodes, double epsilon) } return; } - + // Chinese conversion. NSString *buffer = _composingBuffer; BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; if (chineseConversionEnabled) { buffer = [OpenCCBridge convert:_composingBuffer]; } - + // commit the text, clear the state [client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; _builder->clear(); @@ -413,6 +415,8 @@ static double FindHighestScore(const vector& nodes, double epsilon) [_composingBuffer setString:@""]; gCurrentCandidateController.visible = NO; [_candidates removeAllObjects]; + + [self _hideTooltip]; } NS_INLINE size_t min(size_t a, size_t b) { return a < b ? a : b; } @@ -423,13 +427,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { // "updating the composing buffer" means to request the client to "refresh" the text input buffer // with our "composing text" - + [_composingBuffer setString:@""]; NSInteger composedStringCursorIndex = 0; - + size_t readingCursorIndex = 0; size_t builderCursorIndex = _builder->cursorIndex(); - + // we must do some Unicode codepoint counting to find the actual cursor location for the client // i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars // locations @@ -438,10 +442,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } string nodeStr = (*wi).node->currentKeyValue().value; vector codepoints = OVUTF8Helper::SplitStringByCodePoint(nodeStr); size_t codepointCount = codepoints.size(); - + NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()]; [_composingBuffer appendString:valueString]; - + // this re-aligns the cursor index in the composed string // (the actual cursor on the screen) with the builder's logical // cursor (reading) cursor; each built node has a "spanning length" @@ -461,7 +465,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } } - + // now we gather all the info, we separate the composing buffer to two parts, head and tail, // and insert the reading text (the Mandarin syllable) in between them; // the reading text is what the user is typing @@ -470,7 +474,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } NSString *tail = [_composingBuffer substringFromIndex:composedStringCursorIndex]; NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; NSInteger cursorIndex = composedStringCursorIndex + [reading length]; - + if (_bpmfReadingBuffer->isEmpty() && _builder->markerCursorIndex() != SIZE_MAX) { // if there is a marked range, we need to tear the string into three parts. NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText]; @@ -492,6 +496,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // i.e. the client app needs to take care of where to put ths composing buffer [client setMarkedText:attrString selectionRange:NSMakeRange((NSInteger)_builder->markerCursorIndex(), 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; _latestReadingCursor = (NSInteger)_builder->markerCursorIndex(); + [self _showCurrentMarkedTextTooltipWithClient:client]; } else { // we must use NSAttributedString so that the cursor is visible -- @@ -499,11 +504,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), NSMarkedClauseSegmentAttributeName: @0}; NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText attributes:attrDict]; - + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, // i.e. the client app needs to take care of where to put ths composing buffer [client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; _latestReadingCursor = cursorIndex; + [self _hideTooltip]; } } @@ -513,19 +519,19 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // of the best possible Mandarain characters given the input syllables, // using the Viterbi algorithm implemented in the Gramambular library Walker walker(&_builder->grid()); - + // the reverse walk traces the trellis from the end _walkedNodes = walker.reverseWalk(_builder->grid().width()); - + // then we reverse the nodes so that we get the forward-walked nodes reverse(_walkedNodes.begin(), _walkedNodes.end()); - + // if DEBUG is defined, a GraphViz file is written to kGraphVizOutputfile #if DEBUG string dotDump = _builder->grid().dumpDOT(); NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()]; NSError *error = nil; - + BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile atomically:YES encoding:NSUTF8StringEncoding error:&error]; #endif } @@ -539,10 +545,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // lose their influence over the whole MLE anyway -- so tht when // the user type along, the already composed text at front will // be popped out - + NSInteger _composingBufferSize = [[NSUserDefaults standardUserDefaults] integerForKey:kComposingBufferSizePreferenceKey]; NSInteger previousComposingBufferSize = _composingBufferSize; - + if (_composingBufferSize == 0) { _composingBufferSize = kDefaultComposingBufferSize; } @@ -552,11 +558,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else if (_composingBufferSize > kMaxComposingBufferSize) { _composingBufferSize = kMaxComposingBufferSize; } - + if (_composingBufferSize != previousComposingBufferSize) { [[NSUserDefaults standardUserDefaults] setInteger:_composingBufferSize forKey:kComposingBufferSizePreferenceKey]; } - + if (_builder->grid().width() > (size_t)_composingBufferSize) { if (_walkedNodes.size() > 0) { NodeAnchor &anchor = _walkedNodes[0]; @@ -570,7 +576,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } _builder->removeHeadReadings(anchor.spanningLength); } } - + [self walk]; } @@ -609,46 +615,6 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return layout; } -- (NSString *)_currentMarkedText -{ - if (_builder->markerCursorIndex() < 0) { - return @""; - } - if (!_bpmfReadingBuffer->isEmpty()) { - return @""; - } - - size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); - size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); - // A phrase should contian at least two characters. - if (end - begin < 2) { - return @""; - } - - NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); - NSString *phrase = [_composingBuffer substringWithRange:range]; - NSMutableString *string = [[NSMutableString alloc] init]; - [string appendString:phrase]; - [string appendString:@" "]; - NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; - vector v = _builder->readingsAtRange(begin, end); - for(vector::iterator it_i=v.begin(); it_i!=v.end(); ++it_i) { - [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; - } - [string appendString:[readingsArray componentsJoinedByString:@"-"]]; - return string; -} - -- (BOOL)_writeUserPhrase -{ - NSString *currentMarkedPhrase = [self _currentMarkedText]; - if (![currentMarkedPhrase length]) { - return NO; - } - - return [LanguageModelManager writeUserPhrase:currentMarkedPhrase]; -} - - (vChewingEmacsKey)_detectEmacsKeyFromCharCode:(UniChar)charCode modifiers:(NSUInteger)flags { if (flags & NSControlKeyMask) { @@ -679,10 +645,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { NSRect textFrame = NSZeroRect; NSDictionary *attributes = nil; - + bool composeReading = false; BOOL useVerticalMode = NO; - + @try { attributes = [client attributesForCharacterIndex:0 lineHeightRectangle:&textFrame]; useVerticalMode = [attributes objectForKey:@"IMKTextOrientation"] && [[attributes objectForKey:@"IMKTextOrientation"] integerValue] == 0; @@ -690,35 +656,35 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } @catch (NSException *e) { // exception may raise while using Twitter.app's search filed. } - + NSInteger cursorForwardKey = useVerticalMode ? kDownKeyCode : kRightKeyCode; NSInteger cursorBackwardKey = useVerticalMode ? kUpKeyCode : kLeftKeyCode; NSInteger extraChooseCandidateKey = useVerticalMode ? kLeftKeyCode : kDownKeyCode; NSInteger absorbedArrowKey = useVerticalMode ? kRightKeyCode : kUpKeyCode; NSInteger verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : 0; - + // get the unicode character code UniChar charCode = [inputText length] ? [inputText characterAtIndex:0] : 0; vChewingEmacsKey emacsKey = [self _detectEmacsKeyFromCharCode:charCode modifiers:flags]; - + if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) { // special handling for com.apple.Terminal _currentDeferredClient = client; } - + // if the inputText is empty, it's a function key combination, we ignore it if (![inputText length]) { return NO; } - + // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it if (![_composingBuffer length] && _bpmfReadingBuffer->isEmpty() && ((flags & NSCommandKeyMask) || (flags & NSControlKeyMask) || (flags & NSAlternateKeyMask) || (flags & NSNumericPadKeyMask))) { return NO; } - + // Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo. if (charCode == 8 || charCode == 13 || keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey || keyCode == cursorForwardKey || keyCode == cursorBackwardKey) { // do nothing if backspace is pressed -- we ignore the key @@ -728,40 +694,40 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if ([_composingBuffer length]) { [self commitComposition:client]; } - + // first commit everything in the buffer. if (flags & NSShiftKeyMask) { return NO; } - + // if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char insertions. if (charCode < 0x80 && !isprint(charCode)) { return NO; } - + // when shift is pressed, don't do further processing, since it outputs capital letter anyway. NSString *popedText = [inputText lowercaseString]; [client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; return YES; } - + if (flags & NSNumericPadKeyMask) { if (keyCode != kLeftKeyCode && keyCode != kRightKeyCode && keyCode != kDownKeyCode && keyCode != kUpKeyCode && charCode != 32 && isprint(charCode)) { if ([_composingBuffer length]) { [self commitComposition:client]; } - + NSString *popedText = [inputText lowercaseString]; [client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; return YES; } } - + // if we have candidate, it means we need to pass the event to the candidate handler if ([_candidates count]) { return [self _handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode emacsKey:(vChewingEmacsKey)emacsKey]; } - + // If we have marker index. if (_builder->markerCursorIndex() != SIZE_MAX) { // ESC @@ -783,7 +749,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // Shift + Left // Shift + Up in vertical tyinging mode if ((keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) - && (flags & NSShiftKeyMask)) { + && (flags & NSShiftKeyMask)) { if (_builder->markerCursorIndex() > 0) { _builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1); } @@ -795,7 +761,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // Shift + Right // Shift + Down in vertical tyinging mode if ((keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) - && (flags & NSShiftKeyMask)) { + && (flags & NSShiftKeyMask)) { if (_builder->markerCursorIndex() < _builder->length()) { _builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1); } @@ -805,14 +771,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - + _builder->setMarkerCursorIndex(SIZE_MAX); } - + // see if it's valid BPMF reading if (_bpmfReadingBuffer->isValidKey((char)charCode)) { _bpmfReadingBuffer->combineKey((char)charCode); - + // if we have a tone marker, we have to insert the reading to the // builder in other words, if we don't have a tone marker, we just // update the composing buffer @@ -822,50 +788,50 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } } - + // see if we have composition if Enter/Space is hit and buffer is not empty // this is bit-OR'ed so that the tone marker key is also taken into account composeReading |= (!_bpmfReadingBuffer->isEmpty() && (charCode == 32 || charCode == 13)); if (composeReading) { // combine the reading string reading = _bpmfReadingBuffer->syllable().composedString(); - + // see if we have a unigram for this if (!_languageModel->hasUnigramsForKey(reading)) { [self beep]; [self updateClientComposingBuffer:client]; return YES; } - + // and insert it into the lattice _builder->insertReadingAtCursor(reading); - + // then walk the lattice [self popOverflowComposingTextAndWalk:client]; - + // get user override model suggestion string overrideValue = (_inputMode == kSimpBopomofoModeIdentifier) ? "" : - _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); - + _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + if (!overrideValue.empty()) { size_t cursorIndex = [self actualCandidateCursorIndex]; vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); double highestScore = FindHighestScore(nodes, kEpsilon); _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, highestScore); } - + // then update the text _bpmfReadingBuffer->clear(); [self updateClientComposingBuffer:client]; - + if (_inputMode == kSimpBopomofoModeIdentifier) { [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; } - + // and tells the client that the key is consumed return YES; } - + // keyCode 125 = Down, charCode 32 = Space if (_bpmfReadingBuffer->isEmpty() && [_composingBuffer length] > 0 && (keyCode == extraChooseCandidateKey || charCode == 32 || (useVerticalMode && (keyCode == verticalModeOnlyChooseCandidateKey)))) { if (charCode == 32) { @@ -882,17 +848,17 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; } return YES; - + } } [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; return YES; } - + // Esc if (charCode == 27) { BOOL escToClearInputBufferEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kEscToCleanInputBufferKey]; - + if (escToClearInputBufferEnabled) { // if the optioon is enabled, we clear everythiong including the composing // buffer, walked nodes and the reading. @@ -910,10 +876,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // "cancels" the current composed character and revert it to // Bopomofo reading, in odds with the expectation of users from // other platforms - + if (_bpmfReadingBuffer->isEmpty()) { // no nee to beep since the event is deliberately triggered by user - + if (![_composingBuffer length]) { return NO; } @@ -922,11 +888,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } _bpmfReadingBuffer->clear(); } } - + [self updateClientComposingBuffer:client]; return YES; } - + // handle cursor backward if (keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) { if (!_bpmfReadingBuffer->isEmpty()) { @@ -936,7 +902,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![_composingBuffer length]) { return NO; } - + if (flags & NSShiftKeyMask) { // Shift + left if (_builder->cursorIndex() > 0) { @@ -954,11 +920,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } } - + [self updateClientComposingBuffer:client]; return YES; } - + // handle cursor forward if (keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) { if (!_bpmfReadingBuffer->isEmpty()) { @@ -968,7 +934,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![_composingBuffer length]) { return NO; } - + if (flags & NSShiftKeyMask) { // Shift + Right if (_builder->cursorIndex() < _builder->length()) { @@ -985,11 +951,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } } - + [self updateClientComposingBuffer:client]; return YES; } - + if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; @@ -998,7 +964,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![_composingBuffer length]) { return NO; } - + if (_builder->cursorIndex()) { _builder->setCursorIndex(0); } @@ -1006,11 +972,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self beep]; } } - + [self updateClientComposingBuffer:client]; return YES; } - + if (keyCode == kEndKeyCode || emacsKey == vChewingEmacsKeyEnd) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; @@ -1019,7 +985,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![_composingBuffer length]) { return NO; } - + if (_builder->cursorIndex() != _builder->length()) { _builder->setCursorIndex(_builder->length()); } @@ -1027,11 +993,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self beep]; } } - + [self updateClientComposingBuffer:client]; return YES; } - + if (keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; @@ -1039,14 +1005,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - + // Backspace if (charCode == 8) { if (_bpmfReadingBuffer->isEmpty()) { if (![_composingBuffer length]) { return NO; } - + if (_builder->cursorIndex()) { _builder->deleteReadingBeforeCursor(); [self walk]; @@ -1058,18 +1024,18 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else { _bpmfReadingBuffer->backspace(); } - + [self updateClientComposingBuffer:client]; return YES; } - + // Delete if (keyCode == kDeleteKeyCode || emacsKey == vChewingEmacsKeyDelete) { if (_bpmfReadingBuffer->isEmpty()) { if (![_composingBuffer length]) { return NO; } - + if (_builder->cursorIndex() != _builder->length()) { _builder->deleteReadingAfterCursor(); [self walk]; @@ -1081,22 +1047,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else { [self beep]; } - + [self updateClientComposingBuffer:client]; return YES; } - - + // Enter if (charCode == 13) { if (![_composingBuffer length]) { return NO; } - + [self commitComposition:client]; return YES; } - + // punctuation list if ((char)charCode == '`') { if (_languageModel->hasUnigramsForKey(string("_punctuation_list"))) { @@ -1112,7 +1077,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } } - + // if nothing is matched, see if it's a punctuation key for current layout. string layout = [self _currentLayout]; string punctuationNamePrefix = (_halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_")); @@ -1120,13 +1085,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if ([self _handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } - + // if nothing is matched, see if it's a punctuation key. string punctuation = punctuationNamePrefix + string(1, (char)charCode); if ([self _handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } - + // still nothing, then we update the composing buffer (some app has // strange behavior if we don't do this, "thinking" the key is not // actually consumed) @@ -1135,7 +1100,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - + return NO; } @@ -1150,7 +1115,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self beep]; } [self updateClientComposingBuffer:client]; - + if (_inputMode == kSimpBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) { [self collectCandidates]; if ([_candidates count] == 1) { @@ -1171,11 +1136,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } (charCode == 27) || ((_inputMode == kSimpBopomofoModeIdentifier) && (charCode == 8 || keyCode == kDeleteKeyCode)); - + if (cancelCandidateKey) { gCurrentCandidateController.visible = NO; [_candidates removeAllObjects]; - + if (_inputMode == kSimpBopomofoModeIdentifier) { _builder->clear(); _walkedNodes.clear(); @@ -1295,12 +1260,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) { if (gCurrentCandidateController.selectedCandidateIndex == 0) { [self beep]; - + } else { gCurrentCandidateController.selectedCandidateIndex = 0; } - + [self updateClientComposingBuffer:_currentCandidateClient]; return YES; } @@ -1311,7 +1276,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else { gCurrentCandidateController.selectedCandidateIndex = [_candidates count] - 1; } - + [self updateClientComposingBuffer:_currentCandidateClient]; return YES; } @@ -1323,7 +1288,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } break; } } - + [gCurrentCandidateController.keyLabels indexOfObject:inputText]; if (index != NSNotFound) { NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:index]; @@ -1332,15 +1297,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } } - + if (_inputMode == kSimpBopomofoModeIdentifier) { string layout = [self _currentLayout]; string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); string punctuation = string("_punctuation_") + string(1, (char)charCode); - + BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || _languageModel->hasUnigramsForKey(customPunctuation) || _languageModel->hasUnigramsForKey(punctuation); - + if (shouldAutoSelectCandidate) { NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0]; if (candidateIndex != NSUIntegerMax) { @@ -1349,7 +1314,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } } - + [self beep]; [self updateClientComposingBuffer:_currentCandidateClient]; return YES; @@ -1368,17 +1333,17 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (!functionKeyKeyboardLayoutID) { functionKeyKeyboardLayoutID = @"com.apple.keylayout.US"; } - + NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; if (!basisKeyboardLayoutID) { basisKeyboardLayoutID = @"com.apple.keylayout.US"; } - + // If no override is needed, just return NO. if ([functionKeyKeyboardLayoutID isEqualToString:basisKeyboardLayoutID]) { return NO; } - + // Function key pressed. BOOL includeShift = [[NSUserDefaults standardUserDefaults] boolForKey:kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey]; if (([event modifierFlags] & ~NSShiftKeyMask) || (([event modifierFlags] & NSShiftKeyMask) && includeShift)) { @@ -1386,12 +1351,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [client overrideKeyboardWithKeyboardNamed:functionKeyKeyboardLayoutID]; return NO; } - + // Revert back to the basis layout when the function key is released [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; return NO; } - + NSString *inputText = [event characters]; NSInteger keyCode = [event keyCode]; NSUInteger flags = [event modifierFlags]; @@ -1403,24 +1368,30 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } + (VTHorizontalCandidateController *)horizontalCandidateController { static VTHorizontalCandidateController *instance = nil; - @synchronized(self) { - if (!instance) { - instance = [[VTHorizontalCandidateController alloc] init]; - } - } - + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[VTHorizontalCandidateController alloc] init]; + }); return instance; } + (VTVerticalCandidateController *)verticalCandidateController { static VTVerticalCandidateController *instance = nil; - @synchronized(self) { - if (!instance) { - instance = [[VTVerticalCandidateController alloc] init]; - } - } + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[VTVerticalCandidateController alloc] init]; + }); + return instance; +} ++ (TooltipController *)tooltipController +{ + static TooltipController *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[TooltipController alloc] init]; + }); return instance; } @@ -1428,13 +1399,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { // returns the candidate [_candidates removeAllObjects]; - + size_t cursorIndex = [self actualCandidateCursorIndex]; vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); - + // sort the nodes, so that longer nodes (representing longer phrases) are placed at the top of the candidate list stable_sort(nodes.begin(), nodes.end(), NodeAnchorDescendingSorter()); - + // then use the C++ trick to retrieve the candidates for each node at/crossing the cursor for (vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { const vector& candidates = (*ni).node->candidates(); @@ -1447,9 +1418,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (size_t)actualCandidateCursorIndex { size_t cursorIndex = _builder->cursorIndex(); - + BOOL candidatePhraseLocatedAfterCursor = [[NSUserDefaults standardUserDefaults] boolForKey:kSelectPhraseAfterCursorAsCandidatePreferenceKey]; - + if (candidatePhraseLocatedAfterCursor) { // MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase if (cursorIndex < _builder->length()) { @@ -1461,7 +1432,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } ++cursorIndex; } } - + return cursorIndex; } @@ -1469,7 +1440,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { // set the candidate panel style BOOL useHorizontalCandidateList = [[NSUserDefaults standardUserDefaults] boolForKey:kUseHorizontalCandidateListPreferenceKey]; - + if (useVerticalMode) { gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; } @@ -1479,53 +1450,53 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else { gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; } - + // set the attributes for the candidate panel (which uses NSAttributedString) NSInteger textSize = [[NSUserDefaults standardUserDefaults] integerForKey:kCandidateListTextSizeKey]; - + NSInteger keyLabelSize = textSize / 2; if (keyLabelSize < kMinKeyLabelSize) { keyLabelSize = kMinKeyLabelSize; } - + NSString *ctFontName = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateTextFontName]; NSString *klFontName = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeyLabelFontName]; NSString *ckeys = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeys]; - + gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont systemFontOfSize:keyLabelSize]; gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize]; - + NSMutableArray *keyLabels = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", nil]; - + if ([ckeys length] > 1) { [keyLabels removeAllObjects]; for (NSUInteger i = 0, c = [ckeys length]; i < c; i++) { [keyLabels addObject:[ckeys substringWithRange:NSMakeRange(i, 1)]]; } } - + gCurrentCandidateController.keyLabels = keyLabels; [self collectCandidates]; - + if (_inputMode == kSimpBopomofoModeIdentifier && [_candidates count] == 1) { [self commitComposition:client]; return; } - + gCurrentCandidateController.delegate = self; [gCurrentCandidateController reloadData]; - + // update the composing text, set the client [self updateClientComposingBuffer:client]; _currentCandidateClient = client; - + NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); - + NSInteger cursor = _latestReadingCursor; if (cursor == [_composingBuffer length] && cursor != 0) { cursor--; } - + // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch @try { [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; @@ -1533,17 +1504,124 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } @catch (NSException *exception) { NSLog(@"%@", exception); } - + if (useVerticalMode) { [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; } else { [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; } - + gCurrentCandidateController.visible = YES; } +#pragma mark - User phrases + +- (NSString *)_currentMarkedText +{ + if (_builder->markerCursorIndex() < 0) { + return @""; + } + if (!_bpmfReadingBuffer->isEmpty()) { + return @""; + } + + size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); + size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); + // A phrase should contian at least two characters. + if (end - begin < 1) { + return @""; + } + + NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); + NSString *selectedText = [_composingBuffer substringWithRange:range]; + return selectedText; +} + +- (NSString *)_currentMarkedTextAndReadings +{ + if (_builder->markerCursorIndex() < 0) { + return @""; + } + if (!_bpmfReadingBuffer->isEmpty()) { + return @""; + } + + size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); + size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); + // A phrase should contian at least two characters. + if (end - begin < 2) { + return @""; + } + + NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); + NSString *selectedText = [_composingBuffer substringWithRange:range]; + NSMutableString *string = [[NSMutableString alloc] init]; + [string appendString:selectedText]; + [string appendString:@" "]; + NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; + vector v = _builder->readingsAtRange(begin, end); + for(vector::iterator it_i=v.begin(); it_i!=v.end(); ++it_i) { + [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; + } + [string appendString:[readingsArray componentsJoinedByString:@"-"]]; + return string; +} + +- (BOOL)_writeUserPhrase +{ + NSString *currentMarkedPhrase = [self _currentMarkedTextAndReadings]; + if (![currentMarkedPhrase length]) { + return NO; + } + + return [LanguageModelManager writeUserPhrase:currentMarkedPhrase]; +} + +- (void)_showCurrentMarkedTextTooltipWithClient:(id)client +{ + NSString *text = [self _currentMarkedText]; + NSInteger length = text.length; + if (!length) { + [self _hideTooltip]; + } + else if (length == 1) { + NSString *messsage = [NSString stringWithFormat:NSLocalizedString(@"You are now selecting \"%@\". You can add a phrase with two or more characters.", @""), text]; + [self _showTooltip:messsage client:client]; + } + else { + NSString *messsage = [NSString stringWithFormat:NSLocalizedString(@"You are now selecting \"%@\". Press enter to add a new phrase.", @""), text]; + [self _showTooltip:messsage client:client]; + } +} + +- (void)_showTooltip:(NSString *)tooltip client:(id)client +{ + NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); + + NSInteger cursor = _latestReadingCursor; + if (cursor == [_composingBuffer length] && cursor != 0) { + cursor--; + } + + // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch + @try { + [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; + } + @catch (NSException *exception) { + NSLog(@"%@", exception); + } + + [[vChewingInputMethodController tooltipController] showTooltip:tooltip atPoint:lineHeightRect.origin]; +} + +- (void)_hideTooltip +{ + if ([vChewingInputMethodController tooltipController].window.isVisible) { + [[vChewingInputMethodController tooltipController] hide]; + } +} + #pragma mark - Misc menu items - (void)showPreferences:(id)sender @@ -1569,7 +1647,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; return NO; } - + return YES; } @@ -1644,21 +1722,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)candidateController:(VTCandidateController *)controller didSelectCandidateAtIndex:(NSUInteger)index { gCurrentCandidateController.visible = NO; - + // candidate selected, override the node with selection string selectedValue = [[_candidates objectAtIndex:index] UTF8String]; - + size_t cursorIndex = [self actualCandidateCursorIndex]; _builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue); if (_inputMode != kSimpBopomofoModeIdentifier) { _userOverrideModel->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); } - + [_candidates removeAllObjects]; - + [self walk]; [self updateClientComposingBuffer:_currentCandidateClient]; - + if (_inputMode == kSimpBopomofoModeIdentifier) { [self commitComposition:_currentCandidateClient]; return; @@ -1666,3 +1744,4 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } @end + diff --git a/Source/CandidateUI/HorizontalCandidateController.swift b/Source/UI/CandidateUI/HorizontalCandidateController.swift similarity index 100% rename from Source/CandidateUI/HorizontalCandidateController.swift rename to Source/UI/CandidateUI/HorizontalCandidateController.swift diff --git a/Source/CandidateUI/VTCandidateController.swift b/Source/UI/CandidateUI/VTCandidateController.swift similarity index 100% rename from Source/CandidateUI/VTCandidateController.swift rename to Source/UI/CandidateUI/VTCandidateController.swift diff --git a/Source/CandidateUI/VerticalCandidateController.swift b/Source/UI/CandidateUI/VerticalCandidateController.swift similarity index 100% rename from Source/CandidateUI/VerticalCandidateController.swift rename to Source/UI/CandidateUI/VerticalCandidateController.swift diff --git a/Source/UI/TooltipUI/TooltipController.swift b/Source/UI/TooltipUI/TooltipController.swift new file mode 100644 index 000000000..eb83e5f46 --- /dev/null +++ b/Source/UI/TooltipUI/TooltipController.swift @@ -0,0 +1,100 @@ +// +// TooltipContainer.swift +// +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// Shiki Suen (@ShikiSuen) @ vChewing // Color tweaks only +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +public class TooltipController: NSWindowController { + private let backgroundColor = NSColor.windowBackgroundColor + private var messageTextField: NSTextField + private var tooltip: String = "" { + didSet { + messageTextField.stringValue = tooltip + adjustSize() + } + } + + public init() { + let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0) + let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel] + let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1) + panel.hasShadow = true + + messageTextField = NSTextField() + messageTextField.isEditable = false + messageTextField.isSelectable = false + messageTextField.isBezeled = false + messageTextField.textColor = NSColor.textColor + messageTextField.drawsBackground = true + messageTextField.backgroundColor = backgroundColor + messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small)) + panel.contentView?.addSubview(messageTextField) + + super.init(window: panel) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc(showTooltip:atPoint:) + public func show(tooltip: String, at point: NSPoint) { + self.tooltip = tooltip + window?.orderFront(nil) + set(windowLocation: point) + } + + @objc + public func hide() { + window?.orderOut(nil) + } + + private func set(windowLocation location: NSPoint) { + var newPoint = location + if location.y > 5 { + newPoint.y -= 5 + } + window?.setFrameTopLeftPoint(newPoint) + } + + private func adjustSize() { + let attrString = messageTextField.attributedStringValue; + var rect = attrString.boundingRect(with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin) + rect.size.width += 10 + messageTextField.frame = rect + window?.setFrame(rect, display: true) + } + +} diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 8d205a43d..f2c3bf798 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -25,3 +25,5 @@ "Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\"."; "Edit Excluded Phrases" = "Edit Excluded Phrases"; "Use Half-Width Punctuations" = "Use Half-Width Punctuations"; +"You are now selecting \"%@\". You can add a phrase with two or more characters." = "You are now selecting \"%@\". You can add a phrase with two or more characters."; +"You are now selecting \"%@\". Press enter to add a new phrase." = "You are now selecting \"%@\". Press enter to add a new phrase."; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 15ef591e0..9bd59dd9a 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -25,3 +25,5 @@ "Please check the permission of at \"%@\"." = "请检查此处的存取权限:\"%@\"."; "Edit Excluded Phrases" = "编辑要滤除的语汇"; "Use Half-Width Punctuations" = "啟用半角標點輸出"; +"You are now selecting \"%@\". You can add a phrase with two or more characters." = "您目前选择了「%@」。请选择至少两个字,才能将其加入自订语汇。"; +"You are now selecting \"%@\". Press enter to add a new phrase." = "您目前选择了「%@」。按下 Enter 就可以加入到自订语汇中。"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 385f29311..2f4269529 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -25,3 +25,5 @@ "Please check the permission of at \"%@\"." = "請檢查此處的存取權限:\"%@\"."; "Edit Excluded Phrases" = "編輯要濾除的語彙"; "Use Half-Width Punctuations" = "啟用半形標點輸出"; +"You are now selecting \"%@\". You can add a phrase with two or more characters." = "您目前選擇了「%@」。請選擇至少兩個字,才能將其加入自訂語彙。"; +"You are now selecting \"%@\". Press enter to add a new phrase." = "您目前選擇了「%@」。按下 Enter 就可以加入到自訂語彙中。"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index e1d75a297..e626fb43e 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */; }; 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D052791DA6700838ADB /* AppDelegate.swift */; }; + 5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE798A32792E58A00337FF9 /* TooltipController.swift */; }; 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; @@ -106,6 +107,7 @@ 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; 5BDF2D052791DA6700838ADB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5BE798A32792E58A00337FF9 /* TooltipController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = ""; }; 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; 5BF4A70327844DD0007DC6E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; @@ -253,6 +255,23 @@ path = LanguageModel; sourceTree = ""; }; + 5BE798A12792E50F00337FF9 /* UI */ = { + isa = PBXGroup; + children = ( + 5BE798A22792E51F00337FF9 /* TooltipUI */, + 6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */, + ); + path = UI; + sourceTree = ""; + }; + 5BE798A22792E51F00337FF9 /* TooltipUI */ = { + isa = PBXGroup; + children = ( + 5BE798A32792E58A00337FF9 /* TooltipController.swift */, + ); + path = TooltipUI; + sourceTree = ""; + }; 6A0D4E9215FC0CFA00ABF4B3 = { isa = PBXGroup; children = ( @@ -285,8 +304,8 @@ 6A0D4EC215FC0D3C00ABF4B3 /* Source */ = { isa = PBXGroup; children = ( + 5BE798A12792E50F00337FF9 /* UI */, 5B58E87D278413E7003EA2AD /* MITLicense.txt */, - 6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */, 6A38BBDD15FC115800A8A51F /* Data */, 6A0D4F1215FC0EB100ABF4B3 /* Engine */, 6ACA41E715FC1D9000935EF6 /* Installer */, @@ -647,6 +666,7 @@ 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */, 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */, + 5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */, 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, -- Gitee From 9944d4ce9b7784c1447c4cff8e3335104c3591ca Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 00:06:41 +0800 Subject: [PATCH 050/163] Zonble: Notifier UI like Yahoo Kimo IME. - This won't get suppressed by macOS "do-not-disturb" mode. - Beautified by ShikiSuen, inheriting some message format changes from Hiraku's PR1. Co-Authored-By: Hiraku --- Source/InputMethodController.mm | 2 + Source/UI/NotifierUI/NotifierController.swift | 215 ++++++++++++++++++ vChewing.xcodeproj/project.pbxproj | 12 + 3 files changed, 229 insertions(+) create mode 100644 Source/UI/NotifierUI/NotifierController.swift diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 77b57878c..efaa3d669 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1695,6 +1695,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { _chineseConversionEnabled = !_chineseConversionEnabled; [[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey]; + + [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", _chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; } - (void)toggleHalfWidthPunctuation:(id)sender diff --git a/Source/UI/NotifierUI/NotifierController.swift b/Source/UI/NotifierUI/NotifierController.swift new file mode 100644 index 000000000..5ec3780a3 --- /dev/null +++ b/Source/UI/NotifierUI/NotifierController.swift @@ -0,0 +1,215 @@ +// +// NotifierControler.swift +// +// Copyright (c) 2021-2022 The vChewing Project. +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// Shiki Suen (@ShikiSuen) @ vChewing // Color tweaks only +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +private protocol NotifierWindowDelegate: AnyObject { + func windowDidBecomeClicked(_ window: NotifierWindow) +} + +private class NotifierWindow: NSWindow { + weak var clickDelegate: NotifierWindowDelegate? + + override func mouseDown(with event: NSEvent) { + clickDelegate?.windowDidBecomeClicked(self) + } +} + +private let kWindowWidth: CGFloat = 213.0 +private let kWindowHeight: CGFloat = 60.0 + +public class NotifierController: NSWindowController, NotifierWindowDelegate { + private var messageTextField: NSTextField + + private var message: String = "" { + didSet { + let paraStyle = NSMutableParagraphStyle() + paraStyle.setParagraphStyle(NSParagraphStyle.default) + paraStyle.alignment = .center + let attr: [NSAttributedString.Key: AnyObject] = [ + .foregroundColor: foregroundColor, + .font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)), + .paragraphStyle: paraStyle + ] + let attrString = NSAttributedString(string: message, attributes: attr) + messageTextField.attributedStringValue = attrString + let width = window?.frame.width ?? kWindowWidth + let rect = attrString.boundingRect(with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin) + let height = rect.height + let x = messageTextField.frame.origin.x + let y = ((window?.frame.height ?? kWindowHeight) - height) / 2 + let newFrame = NSRect(x: x, y: y, width: width, height: height) + messageTextField.frame = newFrame + } + } + private var shouldStay: Bool = false + private var backgroundColor: NSColor = .textBackgroundColor { + didSet { + self.window?.backgroundColor = backgroundColor + } + } + private var foregroundColor: NSColor = .controlTextColor { + didSet { + self.messageTextField.textColor = foregroundColor + } + } + private var waitTimer: Timer? + private var fadeTimer: Timer? + + private static var instanceCount = 0 + private static var lastLocation = NSPoint.zero + + @objc public static func notify(message: String, stay: Bool = false) { + let controller = NotifierController() + controller.message = message + controller.shouldStay = stay + controller.show() + } + + private static func increaseInstanceCount() { + instanceCount += 1 + } + + private static func decreaseInstanceCount() { + instanceCount -= 1 + if instanceCount < 0 { + instanceCount = 0 + } + } + + private init() { + let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero + let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight) + var windowRect = contentRect + windowRect.origin.x = screenRect.maxX - windowRect.width - 10 + windowRect.origin.y = screenRect.maxY - windowRect.height - 10 + let styleMask: NSWindow.StyleMask = [.fullSizeContentView, .titled] + + + + let panel = NotifierWindow(contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false) + panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel)) + panel.hasShadow = true + panel.backgroundColor = backgroundColor + panel.title = "" + panel.titlebarAppearsTransparent = true + panel.titleVisibility = .hidden + panel.showsToolbarButton = false + panel.standardWindowButton(NSWindow.ButtonType.fullScreenButton)?.isHidden = true + panel.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)?.isHidden = true + panel.standardWindowButton(NSWindow.ButtonType.closeButton)?.isHidden = true + panel.standardWindowButton(NSWindow.ButtonType.zoomButton)?.isHidden = true + + messageTextField = NSTextField() + messageTextField.frame = contentRect + messageTextField.isEditable = false + messageTextField.isSelectable = false + messageTextField.isBezeled = false + messageTextField.textColor = foregroundColor + messageTextField.drawsBackground = false + messageTextField.font = .boldSystemFont(ofSize: NSFont.systemFontSize(for: .regular)) + panel.contentView?.addSubview(messageTextField) + + super.init(window: panel) + + panel.clickDelegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func show() { + func setStartLocation() { + if NotifierController.instanceCount == 0 { + return + } + let lastLocation = NotifierController.lastLocation + let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero + var windowRect = self.window?.frame ?? NSRect.zero + windowRect.origin.x = lastLocation.x + windowRect.origin.y = lastLocation.y - 10 - windowRect.height + + if windowRect.origin.y < screenRect.minY { + return + } + + self.window?.setFrame(windowRect, display: true) + } + + func moveIn() { + let afterRect = self.window?.frame ?? NSRect.zero + NotifierController.lastLocation = afterRect.origin + var beforeRect = afterRect + beforeRect.origin.y += 10 + window?.setFrame(beforeRect, display: true) + window?.orderFront(self) + window?.setFrame(afterRect, display: true, animate: true) + } + + setStartLocation() + moveIn() + NotifierController.increaseInstanceCount() + waitTimer = Timer.scheduledTimer(timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), userInfo: nil, repeats: false) + } + + @objc private func doFadeOut(_ timer: Timer) { + let opacity = self.window?.alphaValue ?? 0 + if opacity <= 0 { + self.close() + } else { + self.window?.alphaValue = opacity - 0.2 + } + } + + @objc private func fadeOut() { + waitTimer?.invalidate() + waitTimer = nil + NotifierController.decreaseInstanceCount() + fadeTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, repeats: true) + } + + public override func close() { + waitTimer?.invalidate() + waitTimer = nil + fadeTimer?.invalidate() + fadeTimer = nil + super.close() + } + + fileprivate func windowDidBecomeClicked(_ window: NotifierWindow) { + self.fadeOut() + } +} diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index e626fb43e..b12f10574 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */; }; 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D052791DA6700838ADB /* AppDelegate.swift */; }; 5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE798A32792E58A00337FF9 /* TooltipController.swift */; }; + 5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE798A62793280C00337FF9 /* NotifierController.swift */; }; 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; @@ -108,6 +109,7 @@ 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; 5BDF2D052791DA6700838ADB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5BE798A32792E58A00337FF9 /* TooltipController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = ""; }; + 5BE798A62793280C00337FF9 /* NotifierController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotifierController.swift; sourceTree = ""; }; 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; 5BF4A70327844DD0007DC6E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; @@ -258,6 +260,7 @@ 5BE798A12792E50F00337FF9 /* UI */ = { isa = PBXGroup; children = ( + 5BE798A52793280C00337FF9 /* NotifierUI */, 5BE798A22792E51F00337FF9 /* TooltipUI */, 6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */, ); @@ -272,6 +275,14 @@ path = TooltipUI; sourceTree = ""; }; + 5BE798A52793280C00337FF9 /* NotifierUI */ = { + isa = PBXGroup; + children = ( + 5BE798A62793280C00337FF9 /* NotifierController.swift */, + ); + path = NotifierUI; + sourceTree = ""; + }; 6A0D4E9215FC0CFA00ABF4B3 = { isa = PBXGroup; children = ( @@ -660,6 +671,7 @@ buildActionMask = 2147483647; files = ( 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */, + 5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */, 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, -- Gitee From 5f0a0bad6fa340471d8a5558c3eee2b142012695 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 10:03:55 +0800 Subject: [PATCH 051/163] Lukhnos: Use a more tolerant parser for user phrases --- Source/Engine/LanguageModel/UserPhrasesLM.cpp | 156 ++++-------------- Source/Engine/LanguageModel/UserPhrasesLM.h | 37 ++--- Source/Engine/vChewing/CMakeLists.txt | 24 +++ Source/Engine/vChewing/KeyValueBlobReader.cpp | 147 +++++++++++++++++ Source/Engine/vChewing/KeyValueBlobReader.h | 101 ++++++++++++ vChewing.xcodeproj/project.pbxproj | 18 ++ 6 files changed, 338 insertions(+), 145 deletions(-) create mode 100644 Source/Engine/vChewing/CMakeLists.txt create mode 100644 Source/Engine/vChewing/KeyValueBlobReader.cpp create mode 100644 Source/Engine/vChewing/KeyValueBlobReader.h diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.cpp b/Source/Engine/LanguageModel/UserPhrasesLM.cpp index 7c88f9e97..548f002e1 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.cpp +++ b/Source/Engine/LanguageModel/UserPhrasesLM.cpp @@ -4,6 +4,7 @@ // Copyright (c) 2011-2022 The OpenVanilla Project. // // Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla // Weizhong Yang (@zonble) @ OpenVanilla // // Permission is hereby granted, free of charge, to any person @@ -29,14 +30,16 @@ // #include "UserPhrasesLM.h" + #include #include #include #include #include -using namespace Formosa::Gramambular; -using namespace vChewing; +#include "KeyValueBlobReader.h" + +namespace vChewing { UserPhrasesLM::UserPhrasesLM() : fd(-1) @@ -72,113 +75,24 @@ bool UserPhrasesLM::open(const char *path) length = (size_t)sb.st_size; - data = mmap(NULL, length, PROT_WRITE, MAP_PRIVATE, fd, 0); + data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); if (!data) { ::close(fd); return false; } - char *head = (char *)data; - char *end = (char *)data + length; - char c; - Row row; - -start: - // EOF -> end - if (head == end) { - goto end; - } - - c = *head; - // \s -> error - if (c == ' ') { - goto error; - } - // \n -> start - else if (c == '\n') { - head++; - goto start; - } - - // \w -> record column star, state1 - row.value = head; - head++; - // fall through to state 1 - -state1: - // EOF -> error - if (head == end) { - goto error; - } - - c = *head; - // \n -> error - if (c == '\n') { - goto error; - } - // \s -> state2 + zero out ending + record column start - else if (c == ' ') { - *head = 0; - head++; - row.key = head; - goto state2; - } - - // \w -> state1 - head++; - goto state1; - -state2: - if (head == end) { - *head = 0; - head++; - keyRowMap[row.key].push_back(row); - goto end; - } - - c = *head; - // \s -> error - if (c == ' ' || c == '\n') { - *head = 0; - head++; - keyRowMap[row.key].push_back(row); - if (c == ' ') { - goto state3; - } - goto start; - } - - // \w -> state 2 - head++; - goto state2; - -state3: - if (head == end) { - *head = 0; - head++; - keyRowMap[row.key].push_back(row); - goto end; + KeyValueBlobReader reader(static_cast(data), length); + KeyValueBlobReader::KeyValue keyValue; + KeyValueBlobReader::State state; + while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) { + // We invert the key and value, since in user phrases, "key" is the phrase value, and "value" is the BPMF reading. + keyRowMap[keyValue.value].emplace_back(keyValue.value, keyValue.key ); } - c = *head; - if (c == '\n') { - goto start; + if (state == KeyValueBlobReader::State::ERROR) { + close(); + return false; } - - head++; - goto state3; - -error: - close(); - return false; - -end: - static const char *space = " "; - Row emptyRow; - emptyRow.key = space; - emptyRow.value = space; - keyRowMap[space].push_back(emptyRow); - return true; } @@ -195,33 +109,29 @@ void UserPhrasesLM::close() void UserPhrasesLM::dump() { - size_t rows = 0; - for (map >::const_iterator i = keyRowMap.begin(), e = keyRowMap.end(); i != e; ++i) { - const vector& r = (*i).second; - for (vector::const_iterator ri = r.begin(), re = r.end(); ri != re; ++ri) { - const Row& row = *ri; - cerr << row.key << " " << row.value << "\n"; - rows++; + for (const auto& entry : keyRowMap) { + const std::vector& rows = entry.second; + for (const auto& row : rows) { + std::cerr << row.key << " " << row.value << "\n"; } } } -const vector UserPhrasesLM::bigramsForKeys(const string& preceedingKey, const string& key) +const std::vector UserPhrasesLM::bigramsForKeys(const std::string& preceedingKey, const std::string& key) { - return vector(); + return std::vector(); } -const vector UserPhrasesLM::unigramsForKey(const string& key) +const std::vector UserPhrasesLM::unigramsForKey(const std::string& key) { - vector v; - map >::const_iterator i = keyRowMap.find(key.c_str()); - - if (i != keyRowMap.end()) { - for (vector::const_iterator ri = (*i).second.begin(), re = (*i).second.end(); ri != re; ++ri) { - Unigram g; - const Row& r = *ri; - g.keyValue.key = r.key; - g.keyValue.value = r.value; + std::vector v; + auto iter = keyRowMap.find(key); + if (iter != keyRowMap.end()) { + const std::vector& rows = iter->second; + for (const auto& row : rows) { + Formosa::Gramambular::Unigram g; + g.keyValue.key = row.key; + g.keyValue.value = row.value; g.score = 0.0; v.push_back(g); } @@ -230,7 +140,9 @@ const vector UserPhrasesLM::unigramsForKey(const string& key) return v; } -bool UserPhrasesLM::hasUnigramsForKey(const string& key) +bool UserPhrasesLM::hasUnigramsForKey(const std::string& key) { - return keyRowMap.find(key.c_str()) != keyRowMap.end(); + return keyRowMap.find(key) != keyRowMap.end(); } + +}; // namespace vChewing diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.h b/Source/Engine/LanguageModel/UserPhrasesLM.h index 2ff27a308..53f93ced5 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.h +++ b/Source/Engine/LanguageModel/UserPhrasesLM.h @@ -4,6 +4,7 @@ // Copyright (c) 2011-2022 The OpenVanilla Project. // // Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla // Weizhong Yang (@zonble) @ OpenVanilla // // Permission is hereby granted, free of charge, to any person @@ -28,11 +29,10 @@ // OTHER DEALINGS IN THE SOFTWARE. // + #ifndef USERPHRASESLM_H #define USERPHRASESLM_H -#include - #include #include #include @@ -40,37 +40,28 @@ namespace vChewing { -using namespace Formosa::Gramambular; - -class UserPhrasesLM : public LanguageModel +class UserPhrasesLM : public Formosa::Gramambular::LanguageModel { public: UserPhrasesLM(); ~UserPhrasesLM(); - + bool open(const char *path); void close(); void dump(); - - virtual const vector bigramsForKeys(const string& preceedingKey, const string& key); - virtual const vector unigramsForKey(const string& key); - virtual bool hasUnigramsForKey(const string& key); - + + virtual const std::vector bigramsForKeys(const std::string& preceedingKey, const std::string& key); + virtual const std::vector unigramsForKey(const std::string& key); + virtual bool hasUnigramsForKey(const std::string& key); + protected: - struct CStringCmp - { - bool operator()(const char* s1, const char* s2) const - { - return strcmp(s1, s2) < 0; - } - }; - struct Row { - const char *key; - const char *value; + Row(std::string_view& k, std::string_view& v) : key(k), value(v) {} + std::string_view key; + std::string_view value; }; - - map, CStringCmp> keyRowMap; + + std::map> keyRowMap; int fd; void *data; size_t length; diff --git a/Source/Engine/vChewing/CMakeLists.txt b/Source/Engine/vChewing/CMakeLists.txt new file mode 100644 index 000000000..7a97530f6 --- /dev/null +++ b/Source/Engine/vChewing/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.17) +project(KeyValueBlobReader) + +set(CMAKE_CXX_STANDARD 17) + +add_library(KeyValueBlobReader KeyValueBlobReader.cpp KeyValueBlobReader.h) + +# Let CMake fetch Google Test for us. +# https://github.com/google/googletest/tree/main/googletest#incorporating-into-an-existing-cmake-project +include(FetchContent) + +FetchContent_Declare( + googletest + # Specify the commit you depend on and update it regularly. + URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +# Test target declarations. +add_executable(KeyValueBlobReadTest KeyValueBlobReaderTest.cpp) +target_link_libraries(KeyValueBlobReadTest gtest_main KeyValueBlobReader) +add_test(NAME KeyValueBlobReadTest COMMAND KeyValueBlobReadTest) diff --git a/Source/Engine/vChewing/KeyValueBlobReader.cpp b/Source/Engine/vChewing/KeyValueBlobReader.cpp new file mode 100644 index 000000000..3319c4c7e --- /dev/null +++ b/Source/Engine/vChewing/KeyValueBlobReader.cpp @@ -0,0 +1,147 @@ +// +// KeyValueBlobReader.cpp +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#include "KeyValueBlobReader.h" + +namespace vChewing { + +KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue* out) { + static auto new_line = [](char c) { return c == '\n' || c == '\r'; }; + static auto blank = [](char c) { return c == ' ' || c == '\t'; }; + static auto blank_or_newline = [](char c) { return blank(c) || new_line(c); }; + static auto content_char = [](char c) { + return !blank(c) && !new_line(c); + }; + + if (state_ == State::ERROR) { + return state_; + } + + const char* key_begin = nullptr; + size_t key_length = 0; + const char* value_begin = nullptr; + size_t value_length = 0; + + while (true) { + state_ = SkipUntilNot(blank_or_newline); + if (state_ != State::CAN_CONTINUE) { + return state_; + } + + // Check if it's a comment line; if so, read until end of line. + if (*current_ != '#') { + break; + } + state_ = SkipUntil(new_line); + if (state_ != State::CAN_CONTINUE) { + return state_; + } + } + + // No need to check whether* current_ is a content_char, since content_char + // is defined as not blank and not new_line. + + key_begin = current_; + state_ = SkipUntilNot(content_char); + if (state_ != State::CAN_CONTINUE) { + goto error; + } + key_length = current_ - key_begin; + + // There should be at least one blank character after the key string. + if (!blank(*current_)) { + goto error; + } + + state_ = SkipUntilNot(blank); + if (state_ != State::CAN_CONTINUE) { + goto error; + } + + if (!content_char(*current_)) { + goto error; + } + + value_begin = current_; + // value must only contain content characters, blanks not are allowed. + // also, there's no need to check the state after this, since we will always + // emit the value. This also avoids the situation where trailing spaces in a + // line would become part of the value. + SkipUntilNot(content_char); + value_length = current_ - value_begin; + + // Unconditionally skip until the end of the line. This prevents the case + // like "foo bar baz\n" where baz should not be treated as the Next key. + SkipUntil(new_line); + + if (out != nullptr) { + *out = KeyValue{ + std::string_view{key_begin, key_length}, + std::string_view{value_begin, value_length}}; + } + state_ = State::HAS_PAIR; + return state_; + +error: + state_ = State::ERROR; + return State::ERROR; +} + +KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot( + const std::function& f) { + while (current_ != end_ &&* current_) { + if (!f(*current_)) { + return State::CAN_CONTINUE; + } + ++current_; + } + + return State::END; +} + +KeyValueBlobReader::State KeyValueBlobReader::SkipUntil( + const std::function& f) { + while (current_ != end_ &&* current_) { + if (f(*current_)) { + return State::CAN_CONTINUE; + } + ++current_; + } + + return State::END; +} + +std::ostream& operator<<(std::ostream& os, + const KeyValueBlobReader::KeyValue& kv) { + os << "(key: " << kv.key << ", value: " << kv.value << ")"; + return os; +} + +} // namespace vChewing diff --git a/Source/Engine/vChewing/KeyValueBlobReader.h b/Source/Engine/vChewing/KeyValueBlobReader.h new file mode 100644 index 000000000..e0991e778 --- /dev/null +++ b/Source/Engine/vChewing/KeyValueBlobReader.h @@ -0,0 +1,101 @@ +// +// KeyValueBlobReader.h +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Lukhnos Liu (@lukhnos) @ OpenVanilla +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#ifndef SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ +#define SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ + +#include +#include +#include +#include + +// A reader for text-based, blank-separated key-value pairs in a binary blob. +// +// This reader is suitable for reading language model files that entirely +// consist of key-value pairs. Leading or trailing spaces are ignored. +// Lines that start with "#" are treated as comments. Values cannot contain +// spaces. Any space after the value string is parsed is ignored. This implies +// that after a blank, anything that comes after the value can be used as +// comment. Both ' ' and '\t' are treated as blank characters, and the parser +// is agnostic to how lines are ended, and so LF, CR LF, and CR are all valid +// line endings. +// +// std::string_view is used to allow returning results efficiently. As a result, +// the blob is a const char* and will never be mutated. This implies, for +// example, read-only mmap can be used to parse large files. +namespace vChewing { + +class KeyValueBlobReader { + public: + enum class State : int { + // There are no more key-value pairs in this blob. + END = 0, + // The reader has produced a new key-value pair. + HAS_PAIR = 1, + // An error is encountered and the parsing stopped. + ERROR = -1, + // Internal-only state: the parser can continue parsing. + CAN_CONTINUE = 2 + }; + + struct KeyValue { + constexpr KeyValue() : key(""), value("") {} + constexpr KeyValue(std::string_view k, std::string_view v) + : key(k), value(v) {} + + bool operator==(const KeyValue& another) const { + return key == another.key && value == another.value; + } + + std::string_view key; + std::string_view value; + }; + + KeyValueBlobReader(const char* blob, size_t size) + : current_(blob), end_(blob + size) {} + + // Parse the next key-value pair and return the state of the reader. If `out` + // is passed, out will be set to the produced key-value pair if there is one. + State Next(KeyValue* out = nullptr); + + private: + State SkipUntil(const std::function& f); + State SkipUntilNot(const std::function& f); + + const char* current_; + const char* end_; + State state_ = State::CAN_CONTINUE; +}; + +std::ostream& operator<<(std::ostream&, const KeyValueBlobReader::KeyValue&); + +} // namespace vChewing + +#endif // SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index b12f10574..b3304943b 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */; }; 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */; }; 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */; }; + 5BC2D2872793B434002C0BEC /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2D2852793B434002C0BEC /* CMakeLists.txt */; }; + 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */; }; 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; @@ -100,6 +102,9 @@ 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D92763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.xib"; sourceTree = ""; }; 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewingInstaller-Bridging-Header.h"; sourceTree = ""; }; + 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyValueBlobReader.h; sourceTree = ""; }; + 5BC2D2852793B434002C0BEC /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeyValueBlobReader.cpp; sourceTree = ""; }; 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalCandidateController.swift; sourceTree = ""; }; 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; }; 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; }; @@ -257,6 +262,16 @@ path = LanguageModel; sourceTree = ""; }; + 5BC2D2832793B434002C0BEC /* vChewing */ = { + isa = PBXGroup; + children = ( + 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */, + 5BC2D2852793B434002C0BEC /* CMakeLists.txt */, + 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */, + ); + path = vChewing; + sourceTree = ""; + }; 5BE798A12792E50F00337FF9 /* UI */ = { isa = PBXGroup; children = ( @@ -365,6 +380,7 @@ 6A0D4F1215FC0EB100ABF4B3 /* Engine */ = { isa = PBXGroup; children = ( + 5BC2D2832793B434002C0BEC /* vChewing */, 5BA8DAFE27928120009C9FFF /* LanguageModel */, 6A0D4F1315FC0EB100ABF4B3 /* Gramambular */, 6A0D4F1F15FC0EB100ABF4B3 /* Mandarin */, @@ -626,6 +642,7 @@ 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */, 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */, 5B000FC4278495AD004F02AC /* SimpBopomofo@2x.tiff in Resources */, + 5BC2D2872793B434002C0BEC /* CMakeLists.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -677,6 +694,7 @@ 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */, + 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */, 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */, 5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */, 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */, -- Gitee From 56f76b71d518395cf51f2d0e150dca305ace3b9f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 11:55:04 +0800 Subject: [PATCH 052/163] Zonble: Pref Module + Interface to choose Conv Engine - We removed the VXHanConvert from the codes inherited from upstream. - OpenCC in vChewing is for converting things to KangXi. It doesn't convert Simplified Chinese to Traditional (nor vise versa). - Fixed a bug to ensure that the candidate keys won't be niled in the preferencesWindowController. - Fixed a bug to ensure that the customized candidate keys will always consist of unique characters. Entries like "1145141919" will be recognized as "1459" prior to the validity check process. --- Source/Base.lproj/preferences.xib | 2 +- Source/Engine/Keyboard/EmacsKeyHelper.swift | 53 ++ Source/InputMethodController.h | 6 - Source/InputMethodController.mm | 586 +++++++------------- Source/OpenCCBridge.swift | 21 +- Source/PreferencesModule.swift | 289 ++++++++++ Source/PreferencesWindowController.swift | 123 ++-- Source/en.lproj/Localizable.strings | 4 +- Source/zh-Hans.lproj/Localizable.strings | 4 +- Source/zh-Hant.lproj/Localizable.strings | 4 +- vChewing.xcodeproj/project.pbxproj | 22 +- 11 files changed, 667 insertions(+), 447 deletions(-) create mode 100644 Source/Engine/Keyboard/EmacsKeyHelper.swift create mode 100644 Source/PreferencesModule.swift diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index 1cb0c9b46..650989601 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -203,7 +203,7 @@ - + diff --git a/Source/Engine/Keyboard/EmacsKeyHelper.swift b/Source/Engine/Keyboard/EmacsKeyHelper.swift new file mode 100644 index 000000000..b5001f4d6 --- /dev/null +++ b/Source/Engine/Keyboard/EmacsKeyHelper.swift @@ -0,0 +1,53 @@ +// +// EmacsKeyHelper.swift +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +@objc enum vChewingEmacsKey: UInt16 { + case none = 0 + case forward = 6 // F + case backward = 2 // B + case home = 1 // A + case end = 5 // E + case delete = 4 // D + case nextPage = 22 // V +} + +class EmacsKeyHelper: NSObject { + @objc static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey { + if flags.contains(.control) { + return vChewingEmacsKey(rawValue: charCode) ?? .none + } + return .none; + } +} diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index 4649650b3..75714bb52 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -80,11 +80,5 @@ // current input mode NSString *_inputMode; - - // if Chinese conversion is enabled - BOOL _chineseConversionEnabled; - - // if half-width punctuation is enabled - BOOL _halfWidthPunctuationEnabled; } @end diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index efaa3d669..11912be47 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -45,8 +45,6 @@ #import "LanguageModelManager.h" #import "vChewing-Swift.h" -@import OpenCC; - // C++ namespace usages using namespace std; using namespace Formosa::Mandarin; @@ -54,40 +52,7 @@ using namespace Formosa::Gramambular; using namespace vChewing; using namespace OpenVanilla; -// default, min and max candidate list text size -static const NSInteger kDefaultCandidateListTextSize = 16; static const NSInteger kMinKeyLabelSize = 10; -static const NSInteger kMinCandidateListTextSize = 12; -static const NSInteger kMaxCandidateListTextSize = 196; - -// default, min and max composing buffer size (in codepoints) -// modern Macs can usually work up to 16 codepoints when the builder still -// walks the grid with good performance; slower Macs (like old PowerBooks) -// will start to sputter beyond 12; such is the algorithmatic complexity -// of the Viterbi algorithm used in the builder library (at O(N^2)) -static const NSInteger kDefaultComposingBufferSize = 10; -static const NSInteger kMinComposingBufferSize = 4; -static const NSInteger kMaxComposingBufferSize = 20; - -// user defaults (app perferences) key names; in this project we use -// NSUserDefaults throughout and do not wrap them in another config object -static NSString *const kKeyboardLayoutPreferenceKey = @"KeyboardLayout"; -static NSString *const kBasisKeyboardLayoutPreferenceKey = @"BasisKeyboardLayout"; // alphanumeric ("ASCII") input basis -static NSString *const kFunctionKeyKeyboardLayoutPreferenceKey = @"FunctionKeyKeyboardLayout"; // alphanumeric ("ASCII") input basis -static NSString *const kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = @"FunctionKeyKeyboardLayoutOverrideIncludeShift"; // whether include shift -static NSString *const kCandidateListTextSizeKey = @"CandidateListTextSize"; -static NSString *const kSelectPhraseAfterCursorAsCandidatePreferenceKey = @"SelectPhraseAfterCursorAsCandidate"; -static NSString *const kUseHorizontalCandidateListPreferenceKey = @"UseHorizontalCandidateList"; -static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize"; -static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey"; -static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey"; -static NSString *const kHalfWidthPunctuationEnabledKey = @"HalfWidthPunctuationEnabledKey"; -static NSString *const kEscToCleanInputBufferKey = @"EscToCleanInputBufferKey"; - -// advanced (usually optional) settings -static NSString *const kCandidateTextFontName = @"CandidateTextFontName"; -static NSString *const kCandidateKeyLabelFontName = @"CandidateKeyLabelFontName"; -static NSString *const kCandidateKeys = @"CandidateKeys"; // input modes static NSString *const kBopomofoModeIdentifier = @"org.openvanilla.inputmethod.vChewing.Bopomofo"; @@ -107,16 +72,6 @@ enum { kDeleteKeyCode = 117 }; -typedef NS_ENUM(NSUInteger, vChewingEmacsKey) { - vChewingEmacsKeyNone, - vChewingEmacsKeyForward, - vChewingEmacsKeyBackward, - vChewingEmacsKeyHome, - vChewingEmacsKeyEnd, - vChewingEmacsKeyDelete, - vChewingEmacsKeyNextPage, -}; - VTCandidateController *gCurrentCandidateController = nil; // if DEBUG is defined, a DOT file (GraphViz format) will be written to the @@ -131,12 +86,6 @@ static inline NSString *LocalizationNotNeeded(NSString *s) { return s; } -// private methods -@interface vChewingInputMethodController () -+ (VTHorizontalCandidateController *)horizontalCandidateController; -+ (VTVerticalCandidateController *)verticalCandidateController; -@end - @interface vChewingInputMethodController (VTCandidateController) @end @@ -169,7 +118,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) if (_bpmfReadingBuffer) { delete _bpmfReadingBuffer; } - + if (_builder) { delete _builder; } @@ -181,31 +130,29 @@ static double FindHighestScore(const vector& nodes, double epsilon) { // an instance is initialized whenever a text input client (a Mac app) requires // text input from an IME - + self = [super initWithServer:server delegate:delegate client:client]; if (self) { _candidates = [[NSMutableArray alloc] init]; - + // create the reading buffer _bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout()); - + // create the lattice builder _languageModel = [LanguageModelManager languageModelBopomofo]; _userOverrideModel = [LanguageModelManager userOverrideModel]; - + _builder = new BlockReadingBuilder(_languageModel); - + // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); - + // create the composing buffer _composingBuffer = [[NSMutableString alloc] init]; - + _inputMode = kBopomofoModeIdentifier; - _chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; - _halfWidthPunctuationEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kHalfWidthPunctuationEnabledKey]; } - + return self; } @@ -213,18 +160,18 @@ static double FindHighestScore(const vector& nodes, double epsilon) { // a menu instance (autoreleased) is requested every time the user click on the input menu NSMenu *menu = [[NSMenu alloc] initWithTitle:LocalizationNotNeeded(@"Input Method Menu")]; - NSMenuItem *preferenceMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; - [menu addItem:preferenceMenuItem]; - + + [menu addItemWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; + NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; - chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; + chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; [menu addItem:chineseConversionMenuItem]; - + NSMenuItem *halfWidthPunctuationMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""]; - halfWidthPunctuationMenuItem.state = _halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; + halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; [menu addItem:halfWidthPunctuationMenuItem]; - + [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ if (_inputMode == kSimpBopomofoModeIdentifier) { @@ -232,23 +179,15 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItem:editExcludedPhrasesItem]; } else { - NSMenuItem *editUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; - [menu addItem:editUserPhrasesItem]; - - NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesvChewing:) keyEquivalent:@""]; - [menu addItem:editExcludedPhrasesItem]; + [menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; + [menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesvChewing:) keyEquivalent:@""]; } - - NSMenuItem *reloadUserPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; - [menu addItem:reloadUserPhrasesItem]; + [menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ - - NSMenuItem *updateCheckItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; - [menu addItem:updateCheckItem]; - - NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""]; - [menu addItem:aboutMenuItem]; + + [menu addItemWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; + [menu addItemWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""]; return menu; } @@ -257,67 +196,43 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)activateServer:(id)client { [[NSUserDefaults standardUserDefaults] synchronize]; - + // Override the keyboard layout. Use US if not set. - NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; - if (!basisKeyboardLayoutID) { - basisKeyboardLayoutID = @"com.apple.keylayout.US"; - } + NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - + // reset the state _currentDeferredClient = nil; _currentCandidateClient = nil; _builder->clear(); _walkedNodes.clear(); [_composingBuffer setString:@""]; - + // checks and populates the default settings - NSInteger keyboardLayout = [[NSUserDefaults standardUserDefaults] integerForKey:kKeyboardLayoutPreferenceKey]; - switch (keyboardLayout) { - case 0: + switch (Preferences.keyboardLayout) { + case KeyboardLayoutStandard: _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::StandardLayout()); break; - case 1: + case KeyboardLayoutEten: _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::ETenLayout()); break; - case 2: + case KeyboardLayoutHsu: _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::HsuLayout()); break; - case 3: + case KeyboardLayoutEten26: _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::ETen26Layout()); break; - case 4: + case KeyboardLayoutHanyuPinyin: _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::HanyuPinyinLayout()); break; - case 5: + case KeyboardLayoutIBM: _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::IBMLayout()); break; default: _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::StandardLayout()); - [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:kKeyboardLayoutPreferenceKey]; - } - - // set the size - NSInteger textSize = [[NSUserDefaults standardUserDefaults] integerForKey:kCandidateListTextSizeKey]; - NSInteger previousTextSize = textSize; - if (textSize == 0) { - textSize = kDefaultCandidateListTextSize; - } - else if (textSize < kMinCandidateListTextSize) { - textSize = kMinCandidateListTextSize; + Preferences.keyboardLayout = KeyboardLayoutStandard; } - else if (textSize > kMaxCandidateListTextSize) { - textSize = kMaxCandidateListTextSize; - } - - if (textSize != previousTextSize) { - [[NSUserDefaults standardUserDefaults] setInteger:textSize forKey:kCandidateListTextSizeKey]; - } - if (![[NSUserDefaults standardUserDefaults] objectForKey:kChooseCandidateUsingSpaceKey]) { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kChooseCandidateUsingSpaceKey]; - } - + [(AppDelegate *)[NSApp delegate] checkForUpdate]; } @@ -328,17 +243,17 @@ static double FindHighestScore(const vector& nodes, double epsilon) _bpmfReadingBuffer->clear(); [client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; } - + // commit any residue in the composing buffer [self commitComposition:client]; - + _currentDeferredClient = nil; _currentCandidateClient = nil; - + gCurrentCandidateController.delegate = nil; gCurrentCandidateController.visible = NO; [_candidates removeAllObjects]; - + [self _hideTooltip]; } @@ -346,7 +261,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) { NSString *newInputMode; vChewingLM *newLanguageModel; - + if ([value isKindOfClass:[NSString class]] && [value isEqual:kSimpBopomofoModeIdentifier]) { newInputMode = kSimpBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelSimpBopomofo]; @@ -355,30 +270,27 @@ static double FindHighestScore(const vector& nodes, double epsilon) newInputMode = kBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelBopomofo]; } - + // Only apply the changes if the value is changed if (![_inputMode isEqualToString:newInputMode]) { [[NSUserDefaults standardUserDefaults] synchronize]; - + // Remember to override the keyboard layout again -- treat this as an activate eventy - NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; - if (!basisKeyboardLayoutID) { - basisKeyboardLayoutID = @"com.apple.keylayout.US"; - } + NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - + _inputMode = newInputMode; _languageModel = newLanguageModel; - + if (!_bpmfReadingBuffer->isEmpty()) { _bpmfReadingBuffer->clear(); [self updateClientComposingBuffer:sender]; } - + if ([_composingBuffer length] > 0) { [self commitComposition:sender]; } - + if (_builder) { delete _builder; _builder = new BlockReadingBuilder(_languageModel); @@ -389,6 +301,13 @@ static double FindHighestScore(const vector& nodes, double epsilon) #pragma mark - IMKServerInput protocol methods +- (NSString *)_convertToKangXi:(NSString *)text +{ + // return [VXHanConvert convertToSimplifiedFrom:text]; // VXHanConvert 這個引擎有點落後了,不支援詞組轉換、且修改轉換表的過程很麻煩。 + // OpenCC 引擎別的都還好,就是有點肥。改日換成純 ObjC 的 OpenCC 實現方案。 + return [OpenCCBridge convertToKangXi:text]; +} + - (void)commitComposition:(id)client { // if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper) @@ -400,14 +319,14 @@ static double FindHighestScore(const vector& nodes, double epsilon) } return; } - + // Chinese conversion. NSString *buffer = _composingBuffer; - BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; - if (chineseConversionEnabled) { - buffer = [OpenCCBridge convert:_composingBuffer]; + + if (Preferences.chineseConversionEnabled) { + buffer = [self _convertToKangXi:_composingBuffer]; } - + // commit the text, clear the state [client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; _builder->clear(); @@ -415,7 +334,6 @@ static double FindHighestScore(const vector& nodes, double epsilon) [_composingBuffer setString:@""]; gCurrentCandidateController.visible = NO; [_candidates removeAllObjects]; - [self _hideTooltip]; } @@ -427,13 +345,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { // "updating the composing buffer" means to request the client to "refresh" the text input buffer // with our "composing text" - + [_composingBuffer setString:@""]; NSInteger composedStringCursorIndex = 0; - + size_t readingCursorIndex = 0; size_t builderCursorIndex = _builder->cursorIndex(); - + // we must do some Unicode codepoint counting to find the actual cursor location for the client // i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars // locations @@ -442,10 +360,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } string nodeStr = (*wi).node->currentKeyValue().value; vector codepoints = OVUTF8Helper::SplitStringByCodePoint(nodeStr); size_t codepointCount = codepoints.size(); - + NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()]; [_composingBuffer appendString:valueString]; - + // this re-aligns the cursor index in the composed string // (the actual cursor on the screen) with the builder's logical // cursor (reading) cursor; each built node has a "spanning length" @@ -465,7 +383,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } } - + // now we gather all the info, we separate the composing buffer to two parts, head and tail, // and insert the reading text (the Mandarin syllable) in between them; // the reading text is what the user is typing @@ -474,7 +392,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } NSString *tail = [_composingBuffer substringFromIndex:composedStringCursorIndex]; NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; NSInteger cursorIndex = composedStringCursorIndex + [reading length]; - + if (_bpmfReadingBuffer->isEmpty() && _builder->markerCursorIndex() != SIZE_MAX) { // if there is a marked range, we need to tear the string into three parts. NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText]; @@ -504,7 +422,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), NSMarkedClauseSegmentAttributeName: @0}; NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText attributes:attrDict]; - + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, // i.e. the client app needs to take care of where to put ths composing buffer [client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; @@ -519,19 +437,19 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // of the best possible Mandarain characters given the input syllables, // using the Viterbi algorithm implemented in the Gramambular library Walker walker(&_builder->grid()); - + // the reverse walk traces the trellis from the end _walkedNodes = walker.reverseWalk(_builder->grid().width()); - + // then we reverse the nodes so that we get the forward-walked nodes reverse(_walkedNodes.begin(), _walkedNodes.end()); - + // if DEBUG is defined, a GraphViz file is written to kGraphVizOutputfile #if DEBUG string dotDump = _builder->grid().dumpDOT(); NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()]; NSError *error = nil; - + BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile atomically:YES encoding:NSUTF8StringEncoding error:&error]; #endif } @@ -545,38 +463,23 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // lose their influence over the whole MLE anyway -- so tht when // the user type along, the already composed text at front will // be popped out - - NSInteger _composingBufferSize = [[NSUserDefaults standardUserDefaults] integerForKey:kComposingBufferSizePreferenceKey]; - NSInteger previousComposingBufferSize = _composingBufferSize; - - if (_composingBufferSize == 0) { - _composingBufferSize = kDefaultComposingBufferSize; - } - else if (_composingBufferSize < kMinComposingBufferSize) { - _composingBufferSize = kMinComposingBufferSize; - } - else if (_composingBufferSize > kMaxComposingBufferSize) { - _composingBufferSize = kMaxComposingBufferSize; - } - - if (_composingBufferSize != previousComposingBufferSize) { - [[NSUserDefaults standardUserDefaults] setInteger:_composingBufferSize forKey:kComposingBufferSizePreferenceKey]; - } - - if (_builder->grid().width() > (size_t)_composingBufferSize) { + + NSInteger composingBufferSize = Preferences.composingBufferSize; + + if (_builder->grid().width() > (size_t)composingBufferSize) { if (_walkedNodes.size() > 0) { NodeAnchor &anchor = _walkedNodes[0]; NSString *popedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()]; // Chinese conversion. - BOOL chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; + BOOL chineseConversionEnabled = Preferences.chineseConversionEnabled; if (chineseConversionEnabled) { - popedText = [OpenCCBridge convert:popedText]; + popedText = [self _convertToKangXi:popedText]; } [client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; _builder->removeHeadReadings(anchor.spanningLength); } } - + [self walk]; } @@ -588,67 +491,19 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (string)_currentLayout { - string layout = string("Standard_");; - NSInteger keyboardLayout = [[NSUserDefaults standardUserDefaults] integerForKey:kKeyboardLayoutPreferenceKey]; - switch (keyboardLayout) { - case 0: - layout = string("Standard_"); - break; - case 1: - layout = string("ETen_"); - break; - case 2: - layout = string("ETen26_"); - break; - case 3: - layout = string("Hsu_"); - break; - case 4: - layout = string("HanyuPinyin_"); - break; - case 5: - layout = string("IBM_"); - break; - default: - break; - } + NSString *keyboardLayoutName = Preferences.keyboardLayoutName; + string layout = string(keyboardLayoutName.UTF8String) + string("_"); return layout; } -- (vChewingEmacsKey)_detectEmacsKeyFromCharCode:(UniChar)charCode modifiers:(NSUInteger)flags -{ - if (flags & NSControlKeyMask) { - char c = charCode + 'a' - 1; - if (c == 'a') { - return vChewingEmacsKeyHome; - } - else if (c == 'e') { - return vChewingEmacsKeyEnd; - } - else if (c == 'f') { - return vChewingEmacsKeyForward; - } - else if (c == 'b') { - return vChewingEmacsKeyBackward; - } - else if (c == 'd') { - return vChewingEmacsKeyDelete; - } - else if (c == 'v') { - return vChewingEmacsKeyNextPage; - } - } - return vChewingEmacsKeyNone; -} - - (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client { NSRect textFrame = NSZeroRect; NSDictionary *attributes = nil; - + bool composeReading = false; BOOL useVerticalMode = NO; - + @try { attributes = [client attributesForCharacterIndex:0 lineHeightRectangle:&textFrame]; useVerticalMode = [attributes objectForKey:@"IMKTextOrientation"] && [[attributes objectForKey:@"IMKTextOrientation"] integerValue] == 0; @@ -656,35 +511,35 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } @catch (NSException *e) { // exception may raise while using Twitter.app's search filed. } - + NSInteger cursorForwardKey = useVerticalMode ? kDownKeyCode : kRightKeyCode; NSInteger cursorBackwardKey = useVerticalMode ? kUpKeyCode : kLeftKeyCode; NSInteger extraChooseCandidateKey = useVerticalMode ? kLeftKeyCode : kDownKeyCode; NSInteger absorbedArrowKey = useVerticalMode ? kRightKeyCode : kUpKeyCode; NSInteger verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : 0; - + // get the unicode character code UniChar charCode = [inputText length] ? [inputText characterAtIndex:0] : 0; - - vChewingEmacsKey emacsKey = [self _detectEmacsKeyFromCharCode:charCode modifiers:flags]; - + + vChewingEmacsKey emacsKey = [EmacsKeyHelper detectWithCharCode:charCode flags:flags]; + if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) { // special handling for com.apple.Terminal _currentDeferredClient = client; } - + // if the inputText is empty, it's a function key combination, we ignore it if (![inputText length]) { return NO; } - + // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it if (![_composingBuffer length] && _bpmfReadingBuffer->isEmpty() && ((flags & NSCommandKeyMask) || (flags & NSControlKeyMask) || (flags & NSAlternateKeyMask) || (flags & NSNumericPadKeyMask))) { return NO; } - + // Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo. if (charCode == 8 || charCode == 13 || keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey || keyCode == cursorForwardKey || keyCode == cursorBackwardKey) { // do nothing if backspace is pressed -- we ignore the key @@ -694,40 +549,40 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if ([_composingBuffer length]) { [self commitComposition:client]; } - + // first commit everything in the buffer. if (flags & NSShiftKeyMask) { return NO; } - + // if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char insertions. if (charCode < 0x80 && !isprint(charCode)) { return NO; } - + // when shift is pressed, don't do further processing, since it outputs capital letter anyway. NSString *popedText = [inputText lowercaseString]; [client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; return YES; } - + if (flags & NSNumericPadKeyMask) { if (keyCode != kLeftKeyCode && keyCode != kRightKeyCode && keyCode != kDownKeyCode && keyCode != kUpKeyCode && charCode != 32 && isprint(charCode)) { if ([_composingBuffer length]) { [self commitComposition:client]; } - + NSString *popedText = [inputText lowercaseString]; [client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; return YES; } } - + // if we have candidate, it means we need to pass the event to the candidate handler if ([_candidates count]) { return [self _handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode emacsKey:(vChewingEmacsKey)emacsKey]; } - + // If we have marker index. if (_builder->markerCursorIndex() != SIZE_MAX) { // ESC @@ -747,7 +602,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - // Shift + Left // Shift + Up in vertical tyinging mode + // Shift + left if ((keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) && (flags & NSShiftKeyMask)) { if (_builder->markerCursorIndex() > 0) { @@ -759,9 +614,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - // Shift + Right // Shift + Down in vertical tyinging mode + // Shift + Right if ((keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) - && (flags & NSShiftKeyMask)) { + && (flags & NSShiftKeyMask)) { if (_builder->markerCursorIndex() < _builder->length()) { _builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1); } @@ -771,14 +626,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - + _builder->setMarkerCursorIndex(SIZE_MAX); } - + // see if it's valid BPMF reading if (_bpmfReadingBuffer->isValidKey((char)charCode)) { _bpmfReadingBuffer->combineKey((char)charCode); - + // if we have a tone marker, we have to insert the reading to the // builder in other words, if we don't have a tone marker, we just // update the composing buffer @@ -788,55 +643,55 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } } - + // see if we have composition if Enter/Space is hit and buffer is not empty // this is bit-OR'ed so that the tone marker key is also taken into account composeReading |= (!_bpmfReadingBuffer->isEmpty() && (charCode == 32 || charCode == 13)); if (composeReading) { // combine the reading string reading = _bpmfReadingBuffer->syllable().composedString(); - + // see if we have a unigram for this if (!_languageModel->hasUnigramsForKey(reading)) { [self beep]; [self updateClientComposingBuffer:client]; return YES; } - + // and insert it into the lattice _builder->insertReadingAtCursor(reading); - + // then walk the lattice [self popOverflowComposingTextAndWalk:client]; - + // get user override model suggestion string overrideValue = (_inputMode == kSimpBopomofoModeIdentifier) ? "" : - _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); - + _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + if (!overrideValue.empty()) { size_t cursorIndex = [self actualCandidateCursorIndex]; vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); double highestScore = FindHighestScore(nodes, kEpsilon); _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, highestScore); } - + // then update the text _bpmfReadingBuffer->clear(); [self updateClientComposingBuffer:client]; - + if (_inputMode == kSimpBopomofoModeIdentifier) { [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; } - + // and tells the client that the key is consumed return YES; } - + // keyCode 125 = Down, charCode 32 = Space if (_bpmfReadingBuffer->isEmpty() && [_composingBuffer length] > 0 && (keyCode == extraChooseCandidateKey || charCode == 32 || (useVerticalMode && (keyCode == verticalModeOnlyChooseCandidateKey)))) { if (charCode == 32) { // if the spacebar is NOT set to be a selection key - if (![[NSUserDefaults standardUserDefaults] boolForKey:kChooseCandidateUsingSpaceKey]) { + if (!Preferences.chooseCandidateUsingSpace) { if (_builder->cursorIndex() >= _builder->length()) { [_composingBuffer appendString:@" "]; [self commitComposition:client]; @@ -848,17 +703,17 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; } return YES; - + } } [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; return YES; } - + // Esc if (charCode == 27) { - BOOL escToClearInputBufferEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kEscToCleanInputBufferKey]; - + BOOL escToClearInputBufferEnabled = Preferences.escToCleanInputBuffer; + if (escToClearInputBufferEnabled) { // if the optioon is enabled, we clear everythiong including the composing // buffer, walked nodes and the reading. @@ -876,10 +731,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // "cancels" the current composed character and revert it to // Bopomofo reading, in odds with the expectation of users from // other platforms - + if (_bpmfReadingBuffer->isEmpty()) { // no nee to beep since the event is deliberately triggered by user - + if (![_composingBuffer length]) { return NO; } @@ -888,11 +743,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } _bpmfReadingBuffer->clear(); } } - + [self updateClientComposingBuffer:client]; return YES; } - + // handle cursor backward if (keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) { if (!_bpmfReadingBuffer->isEmpty()) { @@ -902,7 +757,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![_composingBuffer length]) { return NO; } - + if (flags & NSShiftKeyMask) { // Shift + left if (_builder->cursorIndex() > 0) { @@ -920,11 +775,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } } - + [self updateClientComposingBuffer:client]; return YES; } - + // handle cursor forward if (keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) { if (!_bpmfReadingBuffer->isEmpty()) { @@ -934,7 +789,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![_composingBuffer length]) { return NO; } - + if (flags & NSShiftKeyMask) { // Shift + Right if (_builder->cursorIndex() < _builder->length()) { @@ -951,11 +806,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } } - + [self updateClientComposingBuffer:client]; return YES; } - + if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; @@ -964,7 +819,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![_composingBuffer length]) { return NO; } - + if (_builder->cursorIndex()) { _builder->setCursorIndex(0); } @@ -972,11 +827,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self beep]; } } - + [self updateClientComposingBuffer:client]; return YES; } - + if (keyCode == kEndKeyCode || emacsKey == vChewingEmacsKeyEnd) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; @@ -985,7 +840,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![_composingBuffer length]) { return NO; } - + if (_builder->cursorIndex() != _builder->length()) { _builder->setCursorIndex(_builder->length()); } @@ -993,11 +848,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self beep]; } } - + [self updateClientComposingBuffer:client]; return YES; } - + if (keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey) { if (!_bpmfReadingBuffer->isEmpty()) { [self beep]; @@ -1005,14 +860,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - + // Backspace if (charCode == 8) { if (_bpmfReadingBuffer->isEmpty()) { if (![_composingBuffer length]) { return NO; } - + if (_builder->cursorIndex()) { _builder->deleteReadingBeforeCursor(); [self walk]; @@ -1024,18 +879,18 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else { _bpmfReadingBuffer->backspace(); } - + [self updateClientComposingBuffer:client]; return YES; } - + // Delete if (keyCode == kDeleteKeyCode || emacsKey == vChewingEmacsKeyDelete) { if (_bpmfReadingBuffer->isEmpty()) { if (![_composingBuffer length]) { return NO; } - + if (_builder->cursorIndex() != _builder->length()) { _builder->deleteReadingAfterCursor(); [self walk]; @@ -1047,21 +902,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else { [self beep]; } - + [self updateClientComposingBuffer:client]; return YES; } - + // Enter if (charCode == 13) { if (![_composingBuffer length]) { return NO; } - + [self commitComposition:client]; return YES; } - + // punctuation list if ((char)charCode == '`') { if (_languageModel->hasUnigramsForKey(string("_punctuation_list"))) { @@ -1077,21 +932,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } } - + // if nothing is matched, see if it's a punctuation key for current layout. string layout = [self _currentLayout]; - string punctuationNamePrefix = (_halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_")); + string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_"); string customPunctuation = punctuationNamePrefix + layout + string(1, (char)charCode); if ([self _handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } - + // if nothing is matched, see if it's a punctuation key. string punctuation = punctuationNamePrefix + string(1, (char)charCode); if ([self _handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) { return YES; } - + // still nothing, then we update the composing buffer (some app has // strange behavior if we don't do this, "thinking" the key is not // actually consumed) @@ -1100,7 +955,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self updateClientComposingBuffer:client]; return YES; } - + return NO; } @@ -1115,7 +970,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self beep]; } [self updateClientComposingBuffer:client]; - + if (_inputMode == kSimpBopomofoModeIdentifier && _bpmfReadingBuffer->isEmpty()) { [self collectCandidates]; if ([_candidates count] == 1) { @@ -1136,11 +991,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } (charCode == 27) || ((_inputMode == kSimpBopomofoModeIdentifier) && (charCode == 8 || keyCode == kDeleteKeyCode)); - + if (cancelCandidateKey) { gCurrentCandidateController.visible = NO; [_candidates removeAllObjects]; - + if (_inputMode == kSimpBopomofoModeIdentifier) { _builder->clear(); _walkedNodes.clear(); @@ -1260,12 +1115,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) { if (gCurrentCandidateController.selectedCandidateIndex == 0) { [self beep]; - + } else { gCurrentCandidateController.selectedCandidateIndex = 0; } - + [self updateClientComposingBuffer:_currentCandidateClient]; return YES; } @@ -1276,7 +1131,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } else { gCurrentCandidateController.selectedCandidateIndex = [_candidates count] - 1; } - + [self updateClientComposingBuffer:_currentCandidateClient]; return YES; } @@ -1288,7 +1143,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } break; } } - + [gCurrentCandidateController.keyLabels indexOfObject:inputText]; if (index != NSNotFound) { NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:index]; @@ -1297,15 +1152,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } } - + if (_inputMode == kSimpBopomofoModeIdentifier) { string layout = [self _currentLayout]; string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); string punctuation = string("_punctuation_") + string(1, (char)charCode); - + BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || _languageModel->hasUnigramsForKey(customPunctuation) || _languageModel->hasUnigramsForKey(punctuation); - + if (shouldAutoSelectCandidate) { NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0]; if (candidateIndex != NSUIntegerMax) { @@ -1314,7 +1169,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } } - + [self beep]; [self updateClientComposingBuffer:_currentCandidateClient]; return YES; @@ -1329,34 +1184,27 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (BOOL)handleEvent:(NSEvent *)event client:(id)client { if ([event type] == NSFlagsChanged) { - NSString *functionKeyKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kFunctionKeyKeyboardLayoutPreferenceKey]; - if (!functionKeyKeyboardLayoutID) { - functionKeyKeyboardLayoutID = @"com.apple.keylayout.US"; - } - - NSString *basisKeyboardLayoutID = [[NSUserDefaults standardUserDefaults] stringForKey:kBasisKeyboardLayoutPreferenceKey]; - if (!basisKeyboardLayoutID) { - basisKeyboardLayoutID = @"com.apple.keylayout.US"; - } - + NSString *functionKeyKeyboardLayoutID = Preferences.functionKeyboardLayout; + NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; + // If no override is needed, just return NO. if ([functionKeyKeyboardLayoutID isEqualToString:basisKeyboardLayoutID]) { return NO; } - + // Function key pressed. - BOOL includeShift = [[NSUserDefaults standardUserDefaults] boolForKey:kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey]; + BOOL includeShift = Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey; if (([event modifierFlags] & ~NSShiftKeyMask) || (([event modifierFlags] & NSShiftKeyMask) && includeShift)) { // Override the keyboard layout and let the OS do its thing [client overrideKeyboardWithKeyboardNamed:functionKeyKeyboardLayoutID]; return NO; } - + // Revert back to the basis layout when the function key is released [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; return NO; } - + NSString *inputText = [event characters]; NSInteger keyCode = [event keyCode]; NSUInteger flags = [event modifierFlags]; @@ -1399,13 +1247,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { // returns the candidate [_candidates removeAllObjects]; - + size_t cursorIndex = [self actualCandidateCursorIndex]; vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); - + // sort the nodes, so that longer nodes (representing longer phrases) are placed at the top of the candidate list stable_sort(nodes.begin(), nodes.end(), NodeAnchorDescendingSorter()); - + // then use the C++ trick to retrieve the candidates for each node at/crossing the cursor for (vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { const vector& candidates = (*ni).node->candidates(); @@ -1418,10 +1266,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (size_t)actualCandidateCursorIndex { size_t cursorIndex = _builder->cursorIndex(); - - BOOL candidatePhraseLocatedAfterCursor = [[NSUserDefaults standardUserDefaults] boolForKey:kSelectPhraseAfterCursorAsCandidatePreferenceKey]; - - if (candidatePhraseLocatedAfterCursor) { + if (Preferences.selectPhraseAfterCursorAsCandidate) { // MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase if (cursorIndex < _builder->length()) { ++cursorIndex; @@ -1432,71 +1277,70 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } ++cursorIndex; } } - + return cursorIndex; } - (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client { // set the candidate panel style - BOOL useHorizontalCandidateList = [[NSUserDefaults standardUserDefaults] boolForKey:kUseHorizontalCandidateListPreferenceKey]; - + if (useVerticalMode) { gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; } - else if (useHorizontalCandidateList) { + else if (Preferences.useHorizontalCandidateList) { gCurrentCandidateController = [vChewingInputMethodController horizontalCandidateController]; } else { gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; } - + // set the attributes for the candidate panel (which uses NSAttributedString) - NSInteger textSize = [[NSUserDefaults standardUserDefaults] integerForKey:kCandidateListTextSizeKey]; - + NSInteger textSize = Preferences.candidateListTextSize; + NSInteger keyLabelSize = textSize / 2; if (keyLabelSize < kMinKeyLabelSize) { keyLabelSize = kMinKeyLabelSize; } - - NSString *ctFontName = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateTextFontName]; - NSString *klFontName = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeyLabelFontName]; - NSString *ckeys = [[NSUserDefaults standardUserDefaults] stringForKey:kCandidateKeys]; - + + NSString *ctFontName = Preferences.candidateTextFontName; + NSString *klFontName = Preferences.candidateKeyLabelFontName; + NSString *ckeys = Preferences.candidateKeys; + gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont systemFontOfSize:keyLabelSize]; gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize]; - + NSMutableArray *keyLabels = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", nil]; - + if ([ckeys length] > 1) { [keyLabels removeAllObjects]; for (NSUInteger i = 0, c = [ckeys length]; i < c; i++) { [keyLabels addObject:[ckeys substringWithRange:NSMakeRange(i, 1)]]; } } - + gCurrentCandidateController.keyLabels = keyLabels; [self collectCandidates]; - + if (_inputMode == kSimpBopomofoModeIdentifier && [_candidates count] == 1) { [self commitComposition:client]; return; } - + gCurrentCandidateController.delegate = self; [gCurrentCandidateController reloadData]; - + // update the composing text, set the client [self updateClientComposingBuffer:client]; _currentCandidateClient = client; - + NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); - + NSInteger cursor = _latestReadingCursor; if (cursor == [_composingBuffer length] && cursor != 0) { cursor--; } - + // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch @try { [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; @@ -1504,14 +1348,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } @catch (NSException *exception) { NSLog(@"%@", exception); } - + if (useVerticalMode) { [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; } else { [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; } - + gCurrentCandidateController.visible = YES; } @@ -1525,14 +1369,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (!_bpmfReadingBuffer->isEmpty()) { return @""; } - + size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); // A phrase should contian at least two characters. if (end - begin < 1) { return @""; } - + NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); NSString *selectedText = [_composingBuffer substringWithRange:range]; return selectedText; @@ -1546,14 +1390,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (!_bpmfReadingBuffer->isEmpty()) { return @""; } - + size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); // A phrase should contian at least two characters. if (end - begin < 2) { return @""; } - + NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); NSString *selectedText = [_composingBuffer substringWithRange:range]; NSMutableString *string = [[NSMutableString alloc] init]; @@ -1574,7 +1418,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![currentMarkedPhrase length]) { return NO; } - + return [LanguageModelManager writeUserPhrase:currentMarkedPhrase]; } @@ -1598,12 +1442,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)_showTooltip:(NSString *)tooltip client:(id)client { NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); - + NSInteger cursor = _latestReadingCursor; if (cursor == [_composingBuffer length] && cursor != 0) { cursor--; } - + // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch @try { [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; @@ -1611,7 +1455,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } @catch (NSException *exception) { NSLog(@"%@", exception); } - + [[vChewingInputMethodController tooltipController] showTooltip:tooltip atPoint:lineHeightRect.origin]; } @@ -1647,7 +1491,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; return NO; } - + return YES; } @@ -1662,25 +1506,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)openUserPhrases:(id)sender { - NSLog(@"openUserPhrases called"); [self _openUserFile:[LanguageModelManager userPhrasesDataPathBopomofo]]; } - (void)openExcludedPhrasesSimpBopomofo:(id)sender { - NSLog(@"openExcludedPhrasesSimpBopomofo called"); [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathSimpBopomofo]]; } - (void)openExcludedPhrasesvChewing:(id)sender { - NSLog(@"openExcludedPhrasesvChewing called"); [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathBopomofo]]; } - (void)reloadUserPhrases:(id)sender { - NSLog(@"reloadUserPhrases called"); [LanguageModelManager loadUserPhrasesModel]; } @@ -1693,16 +1533,16 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)toggleChineseConverter:(id)sender { - _chineseConversionEnabled = !_chineseConversionEnabled; - [[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey]; - - [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", _chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; + BOOL chineseConversionEnabled = [Preferences toggleChineseConversionEnabled]; + [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; } - (void)toggleHalfWidthPunctuation:(id)sender { - _halfWidthPunctuationEnabled = !_halfWidthPunctuationEnabled; - [[NSUserDefaults standardUserDefaults] setBool:_halfWidthPunctuationEnabled forKey:kHalfWidthPunctuationEnabledKey]; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-result" + [Preferences toogleHalfWidthPunctuationEnabled]; +#pragma GCC diagnostic pop } @end @@ -1724,21 +1564,21 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)candidateController:(VTCandidateController *)controller didSelectCandidateAtIndex:(NSUInteger)index { gCurrentCandidateController.visible = NO; - + // candidate selected, override the node with selection string selectedValue = [[_candidates objectAtIndex:index] UTF8String]; - + size_t cursorIndex = [self actualCandidateCursorIndex]; _builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue); if (_inputMode != kSimpBopomofoModeIdentifier) { _userOverrideModel->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); } - + [_candidates removeAllObjects]; - + [self walk]; [self updateClientComposingBuffer:_currentCandidateClient]; - + if (_inputMode == kSimpBopomofoModeIdentifier) { [self commitComposition:_currentCandidateClient]; return; diff --git a/Source/OpenCCBridge.swift b/Source/OpenCCBridge.swift index adba4a52f..53994db83 100644 --- a/Source/OpenCCBridge.swift +++ b/Source/OpenCCBridge.swift @@ -1,5 +1,5 @@ // -// PreferencesWindowController.swift +// OpenCCBridge.swift // // Copyright (c) 2011-2022 The OpenVanilla Project. // @@ -34,22 +34,23 @@ import Foundation import OpenCC -// Since SwiftyLibreCC only provide Swift classes, we create an NSObject subclass -// in Swift in order to bridge the Swift classes into our Objective-C++ project. -class OpenCCBridge : NSObject { +/// A bridge to let Objctive-C code to access SwiftyOpenCC. +/// +/// Since SwiftyOpenCC only provide Swift classes, we create an NSObject subclass +/// in Swift in order to bridge the Swift classes into our Objective-C++ project. +public class OpenCCBridge: NSObject { private static let shared = OpenCCBridge() private var converter: ChineseConverter? - private override init() { try? converter = ChineseConverter(options: .twStandardRev) super.init() } - @objc static func convert(_ string: String) -> String? { + /// Converts to Simplified Chinese. + /// + /// - Parameter string: Text in Traditional Chinese. + /// - Returns: Text in Simplified Chinese. + @objc public static func convertToKangXi(_ string: String) -> String? { shared.converter?.convert(string) } - - private func convert(_ string: String) -> String? { - converter?.convert(string) - } } diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift new file mode 100644 index 000000000..4e52d9c8d --- /dev/null +++ b/Source/PreferencesModule.swift @@ -0,0 +1,289 @@ +// +// Preferences.swift +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +private let kKeyboardLayoutPreferenceKey = "KeyboardLayout" +private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout"; // alphanumeric ("ASCII") input basi +private let kFunctionKeyKeyboardLayoutPreferenceKey = "FunctionKeyKeyboardLayout"; // alphanumeric ("ASCII") input basi +private let kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = "FunctionKeyKeyboardLayoutOverrideIncludeShift"; // whether include shif +private let kCandidateListTextSizeKey = "CandidateListTextSize" +private let kSelectPhraseAfterCursorAsCandidatePreferenceKey = "SelectPhraseAfterCursorAsCandidate" +private let kUseHorizontalCandidateListPreferenceKey = "UseHorizontalCandidateList" +private let kComposingBufferSizePreferenceKey = "ComposingBufferSize" +private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" +private let kChineseConversionEnabledKey = "ChineseConversionEnabled" +private let kHalfWidthPunctuationEnabledKey = "HalfWidthPunctuationEnable" +private let kEscToCleanInputBufferKey = "EscToCleanInputBuffer" + +private let kCandidateTextFontName = "CandidateTextFontName" +private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" +private let kCandidateKeys = "CandidateKeys" +private let kChineseConversionEngineKey = "ChineseConversionEngine" + +private let kDefaultCandidateListTextSize: CGFloat = 18 +private let kMinKeyLabelSize: CGFloat = 10 +private let kMinCandidateListTextSize: CGFloat = 12 +private let kMaxCandidateListTextSize: CGFloat = 196 + +// default, min and max composing buffer size (in codepoints) +// modern Macs can usually work up to 16 codepoints when the builder still +// walks the grid with good performance; slower Macs (like old PowerBooks) +// will start to sputter beyond 12; such is the algorithmatic complexity +// of the Viterbi algorithm used in the builder library (at O(N^2)) +private let kDefaultComposingBufferSize = 10 +private let kMinComposingBufferSize = 4 +private let kMaxComposingBufferSize = 20 + +private let kDefaultKeys = "123456789" + +// MARK: Property wrappers +@propertyWrapper +struct UserDefault { + let key: String + let defaultValue: Value + var container: UserDefaults = .standard + + var wrappedValue: Value { + get { + return container.object(forKey: key) as? Value ?? defaultValue + } + set { + container.set(newValue, forKey: key) + } + } +} + +@propertyWrapper +struct CandidateListTextSize { + let key: String + let defaultValue: CGFloat = kDefaultCandidateListTextSize + lazy var container: UserDefault = { + UserDefault(key: key, defaultValue: defaultValue) }() + + var wrappedValue: CGFloat { + mutating get { + var value = container.wrappedValue + if value < kMinCandidateListTextSize { + value = kMinCandidateListTextSize + } else if value > kMaxCandidateListTextSize { + value = kMaxCandidateListTextSize + } + return value + } + set { + var value = newValue + if value < kMinCandidateListTextSize { + value = kMinCandidateListTextSize + } else if value > kMaxCandidateListTextSize { + value = kMaxCandidateListTextSize + } + container.wrappedValue = value + } + } +} + +@propertyWrapper +struct ComposingBufferSize { + let key: String + let defaultValue: Int = kDefaultComposingBufferSize + lazy var container: UserDefault = { + UserDefault(key: key, defaultValue: defaultValue) }() + + var wrappedValue: Int { + mutating get { + let currentValue = container.wrappedValue + if currentValue < kMinComposingBufferSize { + return kMinComposingBufferSize + } else if currentValue > kMaxComposingBufferSize { + return kMaxComposingBufferSize + } + return currentValue + } + set { + var value = newValue + if value < kMinComposingBufferSize { + value = kMinComposingBufferSize + } else if value > kMaxComposingBufferSize { + value = kMaxComposingBufferSize + } + container.wrappedValue = value + } + } +} + +@propertyWrapper +struct ComposingKeys { + let key: String + let defaultValue: String? = kCandidateKeys + lazy var container: UserDefault = { + UserDefault(key: key, defaultValue: defaultValue) }() + + var wrappedValue: String? { + mutating get { + let value = container.wrappedValue + if let value = value { + if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return nil + } + } + return value + } + set { + let value = newValue + if let value = value { + if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + container.wrappedValue = nil + return + } + } + container.wrappedValue = value + } + } +} + +// MARK: - +@objc enum KeyboardLayout: Int { + case standard + case eten + case eten26 + case hsu + case hanyuPinyin + case IBM + + var name: String { + switch (self) { + case .standard: + return "Standard" + case .eten: + return "ETen" + case .eten26: + return "ETen26" + case .hsu: + return "Hsu" + case .hanyuPinyin: + return "HanyuPinyin" + case .IBM: + return "IBM" + } + } +} + +@objc enum ChineseConversionEngine: Int { + case openCC + case vxHanConvert + + var name: String { + switch (self) { + case .openCC: + return "OpenCC" + case .vxHanConvert: + return "VXHanConvert" + } + } +} + +// MARK: - +class Preferences: NSObject { + @UserDefault(key: kKeyboardLayoutPreferenceKey, defaultValue: 0) + @objc static var keyboardLayout: Int + + @objc static var keyboardLayoutName: String { + (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name + } + + @UserDefault(key: kBasisKeyboardLayoutPreferenceKey, defaultValue: "com.apple.keylayout.US") + @objc static var basisKeyboardLayout: String + + @UserDefault(key: kFunctionKeyKeyboardLayoutPreferenceKey, defaultValue: "com.apple.keylayout.US") + @objc static var functionKeyboardLayout: String + + @UserDefault(key: kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey, defaultValue: false) + @objc static var functionKeyKeyboardLayoutOverrideIncludeShiftKey: Bool + + @CandidateListTextSize(key: kCandidateListTextSizeKey) + @objc static var candidateListTextSize: CGFloat + + @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreferenceKey, defaultValue: false) + @objc static var selectPhraseAfterCursorAsCandidate: Bool + + @UserDefault(key: kUseHorizontalCandidateListPreferenceKey, defaultValue: false) + @objc static var useHorizontalCandidateList: Bool + + @ComposingBufferSize(key: kComposingBufferSizePreferenceKey) + @objc static var composingBufferSize: Int + + @UserDefault(key: kChooseCandidateUsingSpaceKey, defaultValue: true) + @objc static var chooseCandidateUsingSpace: Bool + + @UserDefault(key: kChineseConversionEnabledKey, defaultValue: false) + @objc static var chineseConversionEnabled: Bool + + @objc static func toggleChineseConversionEnabled() -> Bool { + chineseConversionEnabled = !chineseConversionEnabled + return chineseConversionEnabled + } + + @UserDefault(key: kHalfWidthPunctuationEnabledKey, defaultValue: false) + @objc static var halfWidthPunctuationEnabled: Bool + + @objc static func toogleHalfWidthPunctuationEnabled() -> Bool { + halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled + return halfWidthPunctuationEnabled; + } + + @UserDefault(key: kEscToCleanInputBufferKey, defaultValue: false) + @objc static var escToCleanInputBuffer: Bool + + // MARK: Optional settings + @UserDefault(key: kCandidateTextFontName, defaultValue: nil) + @objc static var candidateTextFontName: String? + + @UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil) + @objc static var candidateKeyLabelFontName: String? + + @ComposingKeys(key: kCandidateKeys) + @objc static var candidateKeys: String? + + @objc static var defaultKeys: String { + kDefaultKeys + } + + @UserDefault(key: kChineseConversionEngineKey, defaultValue: 0) + @objc static var chineseConversionEngine: Int + + @objc static var chineseConversionEngineName: String? { + return ChineseConversionEngine(rawValue: chineseConversionEngine)?.name + } + +} diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index a3ce72d78..2cd38ed29 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -36,9 +36,13 @@ import Cocoa import Carbon -private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout" -private let kCandidateKeys = "CandidateKeys" -private let kDefaultKeys = "123456789" +// Extend the RangeReplaceableCollection to allow it clean duplicated characters. +extension RangeReplaceableCollection where Element: Hashable { + var charDeDuplicate: Self { + var set = Set() + return filter{ set.insert($0).inserted } + } +} // Please note that the class should be exposed as "PreferencesWindowController" // in Objective-C in order to let IMK to see the same class name as @@ -47,55 +51,78 @@ private let kDefaultKeys = "123456789" @IBOutlet weak var fontSizePopUpButton: NSPopUpButton! @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! @IBOutlet weak var selectionKeyComboBox: NSComboBox! - + override func awakeFromNib() { let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] var usKeyboardLayoutItem: NSMenuItem? = nil var chosenItem: NSMenuItem? = nil - + basisKeyboardLayoutButton.menu?.removeAllItems() - - let basisKeyboardLayoutID = UserDefaults.standard.string(forKey: kBasisKeyboardLayoutPreferenceKey) + + let basisKeyboardLayoutID = Preferences.basisKeyboardLayout for source in list { - if let categoryPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceCategory) { - let category = Unmanaged.fromOpaque(categoryPtr).takeUnretainedValue() - if category != kTISCategoryKeyboardInputSource { + + func getString(_ key: CFString) -> String? { + if let ptr = TISGetInputSourceProperty(source, key) { + return String(Unmanaged.fromOpaque(ptr).takeUnretainedValue()) + } + return nil + } + + func getBool(_ key: CFString) -> Bool? { + if let ptr = TISGetInputSourceProperty(source, key) { + return Unmanaged.fromOpaque(ptr).takeUnretainedValue() == kCFBooleanTrue + } + return nil + } + + if let category = getString(kTISPropertyInputSourceCategory) { + if category != String(kTISCategoryKeyboardInputSource) { continue } } else { continue } - - if let asciiCapablePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsASCIICapable) { - let asciiCapable = Unmanaged.fromOpaque(asciiCapablePtr).takeUnretainedValue() - if asciiCapable != kCFBooleanTrue { + + if let asciiCapable = getBool(kTISPropertyInputSourceIsASCIICapable) { + if !asciiCapable { continue } } else { continue } - - if let sourceTypePtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceType) { - let sourceType = Unmanaged.fromOpaque(sourceTypePtr).takeUnretainedValue() - if sourceType != kTISTypeKeyboardLayout { + + if let sourceType = getString(kTISPropertyInputSourceType) { + if sourceType != String(kTISTypeKeyboardLayout) { continue } } else { continue } - - guard let sourceIDPtr = TISGetInputSourceProperty(source, kTISPropertyInputSourceID), - let localizedNamePtr = TISGetInputSourceProperty(source, kTISPropertyLocalizedName) else { - continue - } - - let sourceID = String(Unmanaged.fromOpaque(sourceIDPtr).takeUnretainedValue()) - let localizedName = String(Unmanaged.fromOpaque(localizedNamePtr).takeUnretainedValue()) - + + guard let sourceID = getString(kTISPropertyInputSourceID), + let localizedName = getString(kTISPropertyLocalizedName) else { + continue + } + let menuItem = NSMenuItem() menuItem.title = localizedName menuItem.representedObject = sourceID - + + if let iconPtr = TISGetInputSourceProperty(source, kTISPropertyIconRef) { + let icon = IconRef(iconPtr) + let image = NSImage(iconRef: icon) + + func resize( _ image: NSImage) -> NSImage { + let newImage = NSImage(size: NSSize(width: 16, height: 16)) + newImage.lockFocus() + image.draw(in: NSRect(x: 0, y: 0, width: 16, height: 16)) + newImage.unlockFocus() + return newImage + } + menuItem.image = resize(image) + } + if sourceID == "com.apple.keylayout.US" { usKeyboardLayoutItem = menuItem } @@ -104,41 +131,41 @@ private let kDefaultKeys = "123456789" } basisKeyboardLayoutButton.menu?.addItem(menuItem) } - + basisKeyboardLayoutButton.select(chosenItem ?? usKeyboardLayoutItem) selectionKeyComboBox.usesDataSource = false - selectionKeyComboBox.addItems(withObjectValues: [kDefaultKeys, "asdfghjkl", "asdfzxcvb"]) - - var candidateSelectionKeys = (UserDefaults.standard.string(forKey: kCandidateKeys) ?? kDefaultKeys) - .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + selectionKeyComboBox.removeAllItems() + selectionKeyComboBox.addItems(withObjectValues: [Preferences.defaultKeys, "ASDFGHJKL", "ASDFZXCVB"]) + + var candidateSelectionKeys = Preferences.candidateKeys ?? Preferences.defaultKeys if candidateSelectionKeys.isEmpty { - candidateSelectionKeys = kDefaultKeys + candidateSelectionKeys = Preferences.defaultKeys } - + selectionKeyComboBox.stringValue = candidateSelectionKeys } - - @IBAction func updateBasisKeyboardLayoutAction(_ sender:Any) { - if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject { - UserDefaults.standard.set(sourceID, forKey: kBasisKeyboardLayoutPreferenceKey) + + @IBAction func updateBasisKeyboardLayoutAction(_ sender: Any) { + if let sourceID = basisKeyboardLayoutButton.selectedItem?.representedObject as? String { + Preferences.basisKeyboardLayout = sourceID } } - + @IBAction func changeSelectionKeyAction(_ sender: Any) { - let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).charDeDuplicate if keys.count != 9 || !keys.canBeConverted(to: .ascii) { - selectionKeyComboBox.stringValue = kDefaultKeys - UserDefaults.standard.removeObject(forKey: kCandidateKeys) + selectionKeyComboBox.stringValue = Preferences.defaultKeys + Preferences.candidateKeys = nil NSSound.beep() return } - + selectionKeyComboBox.stringValue = keys - if keys == kDefaultKeys { - UserDefaults.standard.removeObject(forKey: kCandidateKeys) + if keys == Preferences.defaultKeys { + Preferences.candidateKeys = nil } else { - UserDefaults.standard.set(keys, forKey: kCandidateKeys) + Preferences.candidateKeys = keys } } - + } diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index f2c3bf798..c4f74b437 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -17,8 +17,8 @@ "Visit Website" = "Visit Website"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@"; "Chinese Conversion" = "Convert zh-TW Kanji to KangXi Variants"; -"NotificationSwitchON" = " ON"; -"NotificationSwitchOFF" = " OFF"; +"NotificationSwitchON" = "✔ ON"; +"NotificationSwitchOFF" = "✘ OFF"; "Edit User Phrases" = "Edit User Phrases"; "Reload User Phrases" = "Reload User Phrases"; "Unable to create the user phrase file." = "Unable to create the user phrase file."; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 9bd59dd9a..8f7bae555 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -17,8 +17,8 @@ "Visit Website" = "前往网站"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),网路上有更新版本 %3$@ (%4$@) 可供下载。是否要前往威注音网站下载新版来安装?%5$@"; "Chinese Conversion" = "优先使用康熙繁体字"; -"NotificationSwitchON" = " 已启用"; -"NotificationSwitchOFF" = " 已停用"; +"NotificationSwitchON" = "✔ 已启用"; +"NotificationSwitchOFF" = "✘ 已停用"; "Edit User Phrases" = "编辑自订语汇"; "Reload User Phrases" = "重载自订语汇"; "Unable to create the user phrase file." = "无法创建自订语汇档案。"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 2f4269529..bdc29ed13 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -17,8 +17,8 @@ "Visit Website" = "前往網站"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),網路上有更新版本 %3$@ (%4$@) 可供下載。是否要前往威注音網站下載新版來安裝?%5$@"; "Chinese Conversion" = "優先使用康熙繁體字"; -"NotificationSwitchON" = " 已啟用"; -"NotificationSwitchOFF" = " 已停用"; +"NotificationSwitchON" = "✔ 已啟用"; +"NotificationSwitchOFF" = "✘ 已停用"; "Edit User Phrases" = "編輯自訂語彙"; "Reload User Phrases" = "重載自訂語彙"; "Unable to create the user phrase file." = "無法創建自訂語彙檔案。"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index b3304943b..861ac85cc 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */; }; 5BC2D2872793B434002C0BEC /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2D2852793B434002C0BEC /* CMakeLists.txt */; }; 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; + 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; }; + 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */; }; 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */; }; 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; @@ -105,6 +107,8 @@ 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyValueBlobReader.h; sourceTree = ""; }; 5BC2D2852793B434002C0BEC /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeyValueBlobReader.cpp; sourceTree = ""; }; + 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmacsKeyHelper.swift; sourceTree = ""; }; + 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesModule.swift; sourceTree = ""; }; 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalCandidateController.swift; sourceTree = ""; }; 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; }; 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; }; @@ -272,6 +276,14 @@ path = vChewing; sourceTree = ""; }; + 5BC2D2892793B8DB002C0BEC /* Keyboard */ = { + isa = PBXGroup; + children = ( + 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */, + ); + path = Keyboard; + sourceTree = ""; + }; 5BE798A12792E50F00337FF9 /* UI */ = { isa = PBXGroup; children = ( @@ -336,7 +348,6 @@ 6A0D4F1215FC0EB100ABF4B3 /* Engine */, 6ACA41E715FC1D9000935EF6 /* Installer */, 6A0D4F4715FC0EB900ABF4B3 /* Resources */, - 5BDF2D052791DA6700838ADB /* AppDelegate.swift */, 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */, 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */, 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */, @@ -345,10 +356,12 @@ 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */, 6A0D4EC815FC0D6400ABF4B3 /* main.m */, 6A0D4EF615FC0DA600ABF4B3 /* vChewing-Prefix.pch */, + 5BDF2D052791DA6700838ADB /* AppDelegate.swift */, + 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */, 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */, - 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */, D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */, - 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */, + 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */, + 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */, D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */, 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */, ); @@ -380,6 +393,7 @@ 6A0D4F1215FC0EB100ABF4B3 /* Engine */ = { isa = PBXGroup; children = ( + 5BC2D2892793B8DB002C0BEC /* Keyboard */, 5BC2D2832793B434002C0BEC /* vChewing */, 5BA8DAFE27928120009C9FFF /* LanguageModel */, 6A0D4F1315FC0EB100ABF4B3 /* Gramambular */, @@ -688,6 +702,7 @@ buildActionMask = 2147483647; files = ( 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */, + 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */, 5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */, 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, @@ -704,6 +719,7 @@ 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */, + 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */, 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, -- Gitee From 7777e967168e37765d2469495fb5af3ad883b49f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 14:14:29 +0800 Subject: [PATCH 053/163] Shiki: More candidate key presets. --- Source/PreferencesWindowController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index 2cd38ed29..5e7b3fe32 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -135,7 +135,7 @@ extension RangeReplaceableCollection where Element: Hashable { basisKeyboardLayoutButton.select(chosenItem ?? usKeyboardLayoutItem) selectionKeyComboBox.usesDataSource = false selectionKeyComboBox.removeAllItems() - selectionKeyComboBox.addItems(withObjectValues: [Preferences.defaultKeys, "ASDFGHJKL", "ASDFZXCVB"]) + selectionKeyComboBox.addItems(withObjectValues: [Preferences.defaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"]) var candidateSelectionKeys = Preferences.candidateKeys ?? Preferences.defaultKeys if candidateSelectionKeys.isEmpty { -- Gitee From 35cc98f9cf4cf271ac858de935340a58b55eea9e Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 14:14:10 +0800 Subject: [PATCH 054/163] Shiki: Fixing how to handle the default candidate keys Update PreferencesWindowController.swift --- Source/PreferencesWindowController.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index 5e7b3fe32..c276871f2 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -155,17 +155,13 @@ extension RangeReplaceableCollection where Element: Hashable { let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).charDeDuplicate if keys.count != 9 || !keys.canBeConverted(to: .ascii) { selectionKeyComboBox.stringValue = Preferences.defaultKeys - Preferences.candidateKeys = nil + Preferences.candidateKeys = Preferences.defaultKeys // 修正記錄:這裡千萬不能是 nil,否則會鬼打牆。 NSSound.beep() return } selectionKeyComboBox.stringValue = keys - if keys == Preferences.defaultKeys { - Preferences.candidateKeys = nil - } else { - Preferences.candidateKeys = keys - } + Preferences.candidateKeys = keys } } -- Gitee From dde3e8d2010b3e8234f5745af794809a989327d6 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 21:18:54 +0800 Subject: [PATCH 055/163] Shiki: Memorize OpenCC toggle status into plist. --- Source/PreferencesModule.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 4e52d9c8d..af17221ee 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -251,6 +251,7 @@ class Preferences: NSObject { @objc static func toggleChineseConversionEnabled() -> Bool { chineseConversionEnabled = !chineseConversionEnabled + UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabledKey) return chineseConversionEnabled } -- Gitee From e53f5cd2d9891724e65803794a893de1eda2823f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 21:52:40 +0800 Subject: [PATCH 056/163] Shiki: Make OpenCC Switch effective in Preferences Window. --- Source/Base.lproj/preferences.xib | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index 650989601..e62722b8c 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -233,6 +233,9 @@ + + + -- Gitee From fe9b8c73325c4bd22def4e62afa2aaf508e55ea1 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 22:45:37 +0800 Subject: [PATCH 057/163] Shiki: OOBE // Write certain default values to user plist if nil. --- Source/AppDelegate.swift | 9 ++- Source/Engine/vChewing/clsOOBEDefaults.swift | 81 ++++++++++++++++++++ Source/InputMethodController.mm | 7 ++ Source/PreferencesModule.swift | 4 +- vChewing.xcodeproj/project.pbxproj | 4 + 5 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 Source/Engine/vChewing/clsOOBEDefaults.swift diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index d29096a13..04af47711 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -68,11 +68,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, LanguageModelManager.loadDataModels() LanguageModelManager.loadUserPhrasesModel() - if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { - UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically) - UserDefaults.standard.synchronize() + OOBE.setMissingDefaults() + + // 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。 + if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true { + checkForUpdate() } - checkForUpdate() } @objc func showPreferences() { diff --git a/Source/Engine/vChewing/clsOOBEDefaults.swift b/Source/Engine/vChewing/clsOOBEDefaults.swift new file mode 100644 index 000000000..b2c059fe3 --- /dev/null +++ b/Source/Engine/vChewing/clsOOBEDefaults.swift @@ -0,0 +1,81 @@ +// +// clsOOBEDefaults.swift +// +// Copyright (c) 2021-2022 The vChewing Project. +// +// Contributors: +// Shiki Suen (@ShikiSuen) @ vChewing +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +private let kCheckUpdateAutomatically = "CheckUpdateAutomatically" +private let kCandidateListTextSize = "CandidateListTextSize" +private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" +private let kSelectPhraseAfterCursorAsCandidate = "SelectPhraseAfterCursorAsCandidate" +private let kUseHorizontalCandidateList = "UseHorizontalCandidateList" +private let kChineseConversionEnabledKey = "ChineseConversionEnabled" + +@objc public class OOBE : NSObject { + + @objc public static func setMissingDefaults () { + // 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。 + + // 首次啟用輸入法時設定不要自動更新,免得在某些要隔絕外部網路連線的保密機構內觸犯資安規則。 + if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { + UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically) + UserDefaults.standard.synchronize() + } + + // 預設選字窗字詞文字尺寸,設成 18 剛剛好 + if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil { + UserDefaults.standard.set(Preferences.candidateListTextSize, forKey: kCandidateListTextSize) + } + + // 預設摁空格鍵來選字,所以設成 true + if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpaceKey) == nil { + UserDefaults.standard.set(Preferences.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpaceKey) + } + + // 預設漢音風格選字,所以要設成 0 + if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidate) == nil { + UserDefaults.standard.set(Preferences.selectPhraseAfterCursorAsCandidate, forKey: kSelectPhraseAfterCursorAsCandidate) + } + + // 預設橫向選字窗,不爽請自行改成縱向選字窗 + if UserDefaults.standard.object(forKey: kUseHorizontalCandidateList) == nil { + UserDefaults.standard.set(Preferences.useHorizontalCandidateList, forKey: kUseHorizontalCandidateList) + } + + // 預設停用繁體轉康熙模組 + if UserDefaults.standard.object(forKey: kChineseConversionEnabledKey) == nil { + UserDefaults.standard.set(Preferences.chineseConversionEnabled, forKey: kChineseConversionEnabledKey) + } + + UserDefaults.standard.synchronize() + } +} diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 11912be47..5f6b808d3 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -195,6 +195,10 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)activateServer:(id)client { + // Write missing OOBE user plist entries. + [OOBE setMissingDefaults]; + + // Read user plist. [[NSUserDefaults standardUserDefaults] synchronize]; // Override the keyboard layout. Use US if not set. @@ -1470,6 +1474,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)showPreferences:(id)sender { + // Write missing OOBE user plist entries. + [OOBE setMissingDefaults]; + // show the preferences panel, and also make the IME app itself the focus if ([IMKInputController instancesRespondToSelector:@selector(showPreferences:)]) { [super showPreferences:sender]; diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index af17221ee..0ad0fb30c 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -214,7 +214,7 @@ struct ComposingKeys { } // MARK: - -class Preferences: NSObject { +@objc public class Preferences: NSObject { @UserDefault(key: kKeyboardLayoutPreferenceKey, defaultValue: 0) @objc static var keyboardLayout: Int @@ -237,7 +237,7 @@ class Preferences: NSObject { @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreferenceKey, defaultValue: false) @objc static var selectPhraseAfterCursorAsCandidate: Bool - @UserDefault(key: kUseHorizontalCandidateListPreferenceKey, defaultValue: false) + @UserDefault(key: kUseHorizontalCandidateListPreferenceKey, defaultValue: true) @objc static var useHorizontalCandidateList: Bool @ComposingBufferSize(key: kComposingBufferSizePreferenceKey) diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 861ac85cc..47e78093f 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC3FB82278492DE0022E99A /* data-chs.txt */; }; + 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */; }; 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; @@ -113,6 +114,7 @@ 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; }; 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; }; 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; + 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = clsOOBEDefaults.swift; sourceTree = ""; }; 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = ""; }; 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; @@ -272,6 +274,7 @@ 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */, 5BC2D2852793B434002C0BEC /* CMakeLists.txt */, 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */, + 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */, ); path = vChewing; sourceTree = ""; @@ -715,6 +718,7 @@ 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, + 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, -- Gitee From 6c15845178915751c7a6b45e58ffe97179f595ad Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 22:48:18 +0800 Subject: [PATCH 058/163] Isaac: Use customized beep sound & fart sound - Initially written by Shiki, rewritten by Isaac to address the issue with duplicated NSSound instances. Co-Authored-By: ix4n33 <16833681+isaacxen@users.noreply.github.com> --- Source/Base.lproj/preferences.xib | 35 ++++++--- Source/Beep.aif | Bin 0 -> 232754 bytes Source/Engine/SFX/clsSFX.swift | 74 +++++++++++++++++++ Source/Engine/vChewing/clsOOBEDefaults.swift | 6 ++ Source/Fart.aif | Bin 0 -> 63414 bytes Source/InputMethodController.mm | 4 +- Source/PreferencesModule.swift | 10 +++ Source/PreferencesWindowController.swift | 7 +- Source/en.lproj/preferences.strings | 3 + Source/zh-Hans.lproj/preferences.strings | 3 + Source/zh-Hant.lproj/preferences.strings | 3 + vChewing.xcodeproj/project.pbxproj | 22 +++++- 12 files changed, 152 insertions(+), 15 deletions(-) create mode 100644 Source/Beep.aif create mode 100644 Source/Engine/SFX/clsSFX.swift create mode 100644 Source/Fart.aif diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index e62722b8c..5d28fd0cf 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -1,7 +1,6 @@ - @@ -218,17 +217,17 @@ + - - - - - - + + + + + + + + + diff --git a/Source/Beep.aif b/Source/Beep.aif new file mode 100644 index 0000000000000000000000000000000000000000..ba0f98952465879463384a516a7c0ec6e05d2027 GIT binary patch literal 232754 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 literal 0 HcmV?d00001 diff --git a/Source/Engine/SFX/clsSFX.swift b/Source/Engine/SFX/clsSFX.swift new file mode 100644 index 000000000..1b184f760 --- /dev/null +++ b/Source/Engine/SFX/clsSFX.swift @@ -0,0 +1,74 @@ +// +// clsSFX.swift +// +// Copyright (c) 2021-2022 The vChewing Project. +// +// Contributors: +// Shiki Suen (@ShikiSuen) @ vChewing +// Isaac Xen a.k.a. ix4n33 (@IsaacXen) @ no affiliation +// +// Based on the Syrup Project and the Formosana Library +// by Lukhnos Liu (@lukhnos). +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Cocoa + +@objc public class clsSFX: NSObject, NSSoundDelegate { + private static let shared = clsSFX() + private override init(){ + super.init() + } + private var currentBeep: NSSound? + private func beep() { + // Stop existing beep + if let beep = currentBeep { + if beep.isPlaying { + beep.stop() + } + } + // Create a new beep sound if possible + var sndBeep:String + if Preferences.shouldNotFartInLieuOfBeep == false { + sndBeep = "Fart" + } else { + sndBeep = "Beep" + } + guard + let beep = NSSound(named:sndBeep) + else { + NSSound.beep() + return + } + beep.delegate = self + beep.volume = 0.4 + beep.play() + currentBeep = beep + } + @objc public func sound(_ sound: NSSound, didFinishPlaying flag: Bool) { + currentBeep = nil + } + @objc static func beep() { + shared.beep() + } +} diff --git a/Source/Engine/vChewing/clsOOBEDefaults.swift b/Source/Engine/vChewing/clsOOBEDefaults.swift index b2c059fe3..56975c8bd 100644 --- a/Source/Engine/vChewing/clsOOBEDefaults.swift +++ b/Source/Engine/vChewing/clsOOBEDefaults.swift @@ -33,6 +33,7 @@ import Cocoa +private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" private let kCheckUpdateAutomatically = "CheckUpdateAutomatically" private let kCandidateListTextSize = "CandidateListTextSize" private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" @@ -75,6 +76,11 @@ private let kChineseConversionEnabledKey = "ChineseConversionEnabled" if UserDefaults.standard.object(forKey: kChineseConversionEnabledKey) == nil { UserDefaults.standard.set(Preferences.chineseConversionEnabled, forKey: kChineseConversionEnabledKey) } + + // 預設沒事不要在那裡放屁 + if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil { + UserDefaults.standard.set(Preferences.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) + } UserDefaults.standard.synchronize() } diff --git a/Source/Fart.aif b/Source/Fart.aif new file mode 100644 index 0000000000000000000000000000000000000000..84c06b3997edfe0754f2137d1e4ddf1748b5376a GIT binary patch literal 63414 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 b ? a : b; } - (void)beep { - // use the system's default sound (configurable in System Preferences) to give a warning - NSBeep(); + // use the vChewing beep. + [clsSFX beep]; } - (string)_currentLayout diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 0ad0fb30c..1413218be 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -45,6 +45,7 @@ private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" private let kChineseConversionEnabledKey = "ChineseConversionEnabled" private let kHalfWidthPunctuationEnabledKey = "HalfWidthPunctuationEnable" private let kEscToCleanInputBufferKey = "EscToCleanInputBuffer" +private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" private let kCandidateTextFontName = "CandidateTextFontName" private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" @@ -245,6 +246,15 @@ struct ComposingKeys { @UserDefault(key: kChooseCandidateUsingSpaceKey, defaultValue: true) @objc static var chooseCandidateUsingSpace: Bool + + @UserDefault(key: kShouldNotFartInLieuOfBeep, defaultValue: true) + @objc static var shouldNotFartInLieuOfBeep: Bool + + @objc static func toggleShouldNotFartInLieuOfBeep() -> Bool { + shouldNotFartInLieuOfBeep = !shouldNotFartInLieuOfBeep + UserDefaults.standard.set(shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) + return shouldNotFartInLieuOfBeep + } @UserDefault(key: kChineseConversionEnabledKey, defaultValue: false) @objc static var chineseConversionEnabled: Bool diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index c276871f2..d05289bf8 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -51,6 +51,7 @@ extension RangeReplaceableCollection where Element: Hashable { @IBOutlet weak var fontSizePopUpButton: NSPopUpButton! @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! @IBOutlet weak var selectionKeyComboBox: NSComboBox! + @IBOutlet weak var clickedWhetherIMEShouldNotFartToggle: NSButton! override func awakeFromNib() { let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] @@ -151,12 +152,16 @@ extension RangeReplaceableCollection where Element: Hashable { } } + @IBAction func clickedWhetherIMEShouldNotFartToggleAction(_ sender: Any) { + clsSFX.beep() + } + @IBAction func changeSelectionKeyAction(_ sender: Any) { let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).charDeDuplicate if keys.count != 9 || !keys.canBeConverted(to: .ascii) { selectionKeyComboBox.stringValue = Preferences.defaultKeys Preferences.candidateKeys = Preferences.defaultKeys // 修正記錄:這裡千萬不能是 nil,否則會鬼打牆。 - NSSound.beep() + clsSFX.beep() return } diff --git a/Source/en.lproj/preferences.strings b/Source/en.lproj/preferences.strings index 76042fc34..da8287521 100644 --- a/Source/en.lproj/preferences.strings +++ b/Source/en.lproj/preferences.strings @@ -101,6 +101,9 @@ /* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ "9DS-Rc-TXq.title" = "UI language setting:"; +/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "ArK-Vk-OoT"; */ +"ArK-Vk-OoT.title" = "Stop farting (when typed phonetic combination is invalid, etc.)"; + /* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ "BSK-bH-Gct.title" = "Auto-convert traditional Chinese glyphs to KangXi characters"; diff --git a/Source/zh-Hans.lproj/preferences.strings b/Source/zh-Hans.lproj/preferences.strings index 572b9b323..0b1869bfb 100644 --- a/Source/zh-Hans.lproj/preferences.strings +++ b/Source/zh-Hans.lproj/preferences.strings @@ -101,6 +101,9 @@ /* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ "9DS-Rc-TXq.title" = "介面语言设定:"; +/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "ArK-Vk-OoT"; */ +"ArK-Vk-OoT.title" = "不要放屁 // 例:当输入的音韵有误时,等。"; + /* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ "BSK-bH-Gct.title" = "自动将繁体中文字转换为康熙字"; diff --git a/Source/zh-Hant.lproj/preferences.strings b/Source/zh-Hant.lproj/preferences.strings index a43aa0754..490bd4544 100644 --- a/Source/zh-Hant.lproj/preferences.strings +++ b/Source/zh-Hant.lproj/preferences.strings @@ -101,6 +101,9 @@ /* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ "9DS-Rc-TXq.title" = "介面語言設定:"; +/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "ArK-Vk-OoT"; */ +"ArK-Vk-OoT.title" = "不要放屁 // 例:當輸入的音韻有誤時,等。"; + /* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ "BSK-bH-Gct.title" = "自動將繁體中文字轉換為康熙字"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 47e78093f..34dcd2cf0 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -23,6 +23,9 @@ 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC3FB82278492DE0022E99A /* data-chs.txt */; }; + 5BD0D19427940E9D0008F48E /* Fart.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19327940E9D0008F48E /* Fart.aif */; }; + 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D19927943D390008F48E /* clsSFX.swift */; }; + 5BD0D19F279454F60008F48E /* Beep.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19E279454F60008F48E /* Beep.aif */; }; 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */; }; 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; @@ -114,6 +117,9 @@ 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; }; 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; }; 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; + 5BD0D19327940E9D0008F48E /* Fart.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.aif; sourceTree = ""; }; + 5BD0D19927943D390008F48E /* clsSFX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = clsSFX.swift; sourceTree = ""; }; + 5BD0D19E279454F60008F48E /* Beep.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.aif; sourceTree = ""; }; 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = clsOOBEDefaults.swift; sourceTree = ""; }; 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = ""; }; 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; @@ -287,6 +293,14 @@ path = Keyboard; sourceTree = ""; }; + 5BD0D19827943D270008F48E /* SFX */ = { + isa = PBXGroup; + children = ( + 5BD0D19927943D390008F48E /* clsSFX.swift */, + ); + path = SFX; + sourceTree = ""; + }; 5BE798A12792E50F00337FF9 /* UI */ = { isa = PBXGroup; children = ( @@ -396,6 +410,7 @@ 6A0D4F1215FC0EB100ABF4B3 /* Engine */ = { isa = PBXGroup; children = ( + 5BD0D19827943D270008F48E /* SFX */, 5BC2D2892793B8DB002C0BEC /* Keyboard */, 5BC2D2832793B434002C0BEC /* vChewing */, 5BA8DAFE27928120009C9FFF /* LanguageModel */, @@ -475,6 +490,8 @@ 6A0D4F4715FC0EB900ABF4B3 /* Resources */ = { isa = PBXGroup; children = ( + 5BD0D19E279454F60008F48E /* Beep.aif */, + 5BD0D19327940E9D0008F48E /* Fart.aif */, 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */, 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */, 6A0D4EEE15FC0DA600ABF4B3 /* Images */, @@ -652,11 +669,13 @@ 5B000FC3278495AD004F02AC /* SimpBopomofo.tiff in Resources */, 6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */, 6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */, - 6A38BC1515FC117A00A8A51F /* data.txt in Resources */, + 5BD0D19427940E9D0008F48E /* Fart.aif in Resources */, + 6A38BC1515FC117A00A8A51F /* data-cht.txt in Resources */, 6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */, 5B58E87F278413E7003EA2AD /* MITLicense.txt in Resources */, 6A187E2616004C5900466B2E /* MainMenu.xib in Resources */, 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */, + 5BD0D19F279454F60008F48E /* Beep.aif in Resources */, 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */, 5B000FC4278495AD004F02AC /* SimpBopomofo@2x.tiff in Resources */, 5BC2D2872793B434002C0BEC /* CMakeLists.txt in Resources */, @@ -706,6 +725,7 @@ files = ( 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */, 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */, + 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */, 5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */, 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, -- Gitee From 62ea65a344bb8316ff5fb959aeea589ea2e2db90 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 16 Jan 2022 22:56:21 +0800 Subject: [PATCH 059/163] Shiki: Preferences // Change CNS11643 description texts. - This feature is not implemented yet. --- Source/Base.lproj/preferences.xib | 2 +- Source/en.lproj/preferences.strings | 6 +++--- Source/zh-Hans.lproj/preferences.strings | 4 ++-- Source/zh-Hant.lproj/preferences.strings | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index 5d28fd0cf..3a88677a1 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -221,7 +221,7 @@ - + diff --git a/Source/en.lproj/preferences.strings b/Source/en.lproj/preferences.strings index da8287521..8fa9fb3b4 100644 --- a/Source/en.lproj/preferences.strings +++ b/Source/en.lproj/preferences.strings @@ -6,7 +6,7 @@ "5.title" = "OtherViews"; /* Class = "NSMenuItem"; title = "Standard"; ObjectID = "6"; */ -"6.title" = "Microsoft Standard / Daqian / Wang / 01"; +"6.title" = "Standard"; /* Class = "NSMenuItem"; title = "ETen"; ObjectID = "7"; */ "7.title" = "ETen"; @@ -134,8 +134,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "Output Settings"; -/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (non-PUA UTF-8 / GB18030 characters only)"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "Enable CNS11643 Support (non-PUA UTF-8 / GB18030 characters only)"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "Enable CNS11643 Support"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "Keyboard Layout"; diff --git a/Source/zh-Hans.lproj/preferences.strings b/Source/zh-Hans.lproj/preferences.strings index 0b1869bfb..2f650fce3 100644 --- a/Source/zh-Hans.lproj/preferences.strings +++ b/Source/zh-Hans.lproj/preferences.strings @@ -134,8 +134,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "输出设定"; -/* Class = "NSButtonCell"; title = "Enable partial CNS11643 Support (non-PUA UTF-8 characters only)"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "启用部分 CNS11643 全字库支援 (仅限非 PUA 区域的 UTF-8 文字)"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "启用 CNS11643 全字库支援"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "键盘布局"; diff --git a/Source/zh-Hant.lproj/preferences.strings b/Source/zh-Hant.lproj/preferences.strings index 490bd4544..1b40d2afd 100644 --- a/Source/zh-Hant.lproj/preferences.strings +++ b/Source/zh-Hant.lproj/preferences.strings @@ -134,8 +134,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "輸出設定"; -/* Class = "NSButtonCell"; title = "Enable partial CNS11643 Support (non-PUA UTF-8 characters only)"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "啟用部分 CNS11643 全字庫支援 (僅限非 PUA 區域的 UTF-8 文字)"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "鍵盤佈局"; -- Gitee From fdfed55de308946c8e35998a067ed957a1664073 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 17 Jan 2022 01:06:15 +0800 Subject: [PATCH 060/163] Zonble: Add in-place phrase replacement system. - Also write the settings to the user plist (OOBE). --- Source/AppDelegate.swift | 3 +- .../LanguageModel/PhraseReplacementMap.cpp | 119 ++++++++++++++++++ .../LanguageModel/PhraseReplacementMap.h | 59 +++++++++ Source/Engine/LanguageModel/vChewingLM.cpp | 55 ++++++-- Source/Engine/LanguageModel/vChewingLM.h | 11 +- Source/Engine/vChewing/clsOOBEDefaults.swift | 6 + Source/InputMethodController.mm | 68 ++++++---- Source/LanguageModelManager.h | 4 +- Source/LanguageModelManager.mm | 25 +++- Source/PreferencesModule.swift | 9 ++ Source/en.lproj/Localizable.strings | 2 + Source/vChewing-Bridging-Header.h | 3 +- Source/zh-Hans.lproj/Localizable.strings | 2 + Source/zh-Hant.lproj/Localizable.strings | 2 + vChewing.xcodeproj/project.pbxproj | 8 ++ 15 files changed, 335 insertions(+), 41 deletions(-) create mode 100644 Source/Engine/LanguageModel/PhraseReplacementMap.cpp create mode 100644 Source/Engine/LanguageModel/PhraseReplacementMap.h diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 04af47711..49a539eaa 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -66,7 +66,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, func applicationDidFinishLaunching(_ notification: Notification) { LanguageModelManager.loadDataModels() - LanguageModelManager.loadUserPhrasesModel() + LanguageModelManager.loadUserPhrases() + LanguageModelManager.loadUserPhraseReplacement() OOBE.setMissingDefaults() diff --git a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp new file mode 100644 index 000000000..06c8bf88e --- /dev/null +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp @@ -0,0 +1,119 @@ +// +// PhraseReplacementMap.cpp +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#include +#include +#include +#include +#include +#include "KeyValueBlobReader.h" +#include "PhraseReplacementMap.h" + +namespace vChewing { + +using std::string; + +PhraseReplacementMap::PhraseReplacementMap() +: fd(-1) +, data(0) +, length(0) +{ +} + +PhraseReplacementMap::~PhraseReplacementMap() +{ + if (data) { + close(); + } +} + +bool PhraseReplacementMap::open(const char *path) +{ + if (data) { + return false; + } + + fd = ::open(path, O_RDONLY); + if (fd == -1) { + printf("open:: file not exist"); + return false; + } + + struct stat sb; + if (fstat(fd, &sb) == -1) { + printf("open:: cannot open file"); + return false; + } + + length = (size_t)sb.st_size; + + data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); + if (!data) { + ::close(fd); + return false; + } + + KeyValueBlobReader reader(static_cast(data), length); + KeyValueBlobReader::KeyValue keyValue; + KeyValueBlobReader::State state; + while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) { + keyValueMap[keyValue.key] = keyValue.value; + } + + if (state == KeyValueBlobReader::State::ERROR) { + close(); + return false; + } + return true; +} + +void PhraseReplacementMap::close() +{ + if (data) { + munmap(data, length); + ::close(fd); + data = 0; + } + + keyValueMap.clear(); +} + +const std::string PhraseReplacementMap::valueForKey(const std::string& key) +{ + auto iter = keyValueMap.find(key); + if (iter != keyValueMap.end()) { + const std::string_view v = iter->second; + return {v.data(), v.size()}; + } + return string(""); +} + + +} diff --git a/Source/Engine/LanguageModel/PhraseReplacementMap.h b/Source/Engine/LanguageModel/PhraseReplacementMap.h new file mode 100644 index 000000000..b775fc502 --- /dev/null +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.h @@ -0,0 +1,59 @@ +// +// PhraseReplacementMap.h +// +// Copyright (c) 2011-2022 The OpenVanilla Project. +// +// Contributors: +// Weizhong Yang (@zonble) @ OpenVanilla +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#ifndef PHRASEREPLACEMENTMAP_H +#define PHRASEREPLACEMENTMAP_H + +#include +#include +#include + +namespace vChewing { + +class PhraseReplacementMap +{ +public: + PhraseReplacementMap(); + ~PhraseReplacementMap(); + + bool open(const char *path); + void close(); + const std::string valueForKey(const std::string& key); + +protected: + std::map keyValueMap; + int fd; + void *data; + size_t length; +}; + +} + +#endif diff --git a/Source/Engine/LanguageModel/vChewingLM.cpp b/Source/Engine/LanguageModel/vChewingLM.cpp index 45b11683d..3cdbf94ef 100644 --- a/Source/Engine/LanguageModel/vChewingLM.cpp +++ b/Source/Engine/LanguageModel/vChewingLM.cpp @@ -50,6 +50,7 @@ vChewingLM::~vChewingLM() m_languageModel.close(); m_userPhrases.close(); m_excludedPhrases.close(); + m_phraseReplacement.close(); } void vChewingLM::loadLanguageModel(const char* languageModelDataPath) @@ -73,6 +74,13 @@ void vChewingLM::loadUserPhrases(const char* userPhrasesDataPath, } } +void vChewingLM::loadPhraseReplacementMap(const char* phraseReplacementPath) { + if (phraseReplacementPath) { + m_phraseReplacement.close(); + m_phraseReplacement.open(phraseReplacementPath); + } +} + const vector vChewingLM::bigramsForKeys(const string& preceedingKey, const string& key) { return vector(); @@ -96,24 +104,45 @@ const vector vChewingLM::unigramsForKey(const string& key) if (m_userPhrases.hasUnigramsForKey(key)) { vector rawUserUnigrams = m_userPhrases.unigramsForKey(key); - + vector filterredUserUnigrams = m_userPhrases.unigramsForKey(key); + for (auto&& unigram : rawUserUnigrams) { if (excludedValues.find(unigram.keyValue.value) == excludedValues.end()) { - userUnigrams.push_back(unigram); + filterredUserUnigrams.push_back(unigram); } } - - transform(userUnigrams.begin(), userUnigrams.end(), + + transform(filterredUserUnigrams.begin(), filterredUserUnigrams.end(), inserter(userValues, userValues.end()), [](const Unigram &u) { return u.keyValue.value; }); + + if (m_phraseReplacementEnabled) { + for (auto&& unigram : filterredUserUnigrams) { + string value = unigram.keyValue.value; + string replacement = m_phraseReplacement.valueForKey(value); + if (replacement != "") { + unigram.keyValue.value = replacement; + } + unigrams.push_back(unigram); + } + } else { + unigrams = filterredUserUnigrams; + } } - + if (m_languageModel.hasUnigramsForKey(key)) { vector globalUnigrams = m_languageModel.unigramsForKey(key); - + for (auto&& unigram : globalUnigrams) { - if (excludedValues.find(unigram.keyValue.value) == excludedValues.end() && - userValues.find(unigram.keyValue.value) == userValues.end()) { + string value = unigram.keyValue.value; + if (excludedValues.find(value) == excludedValues.end() && + userValues.find(value) == userValues.end()) { + if (m_phraseReplacementEnabled) { + string replacement = m_phraseReplacement.valueForKey(value); + if (replacement != "") { + unigram.keyValue.value = replacement; + } + } unigrams.push_back(unigram); } } @@ -132,3 +161,13 @@ bool vChewingLM::hasUnigramsForKey(const string& key) return unigramsForKey(key).size() > 0; } + +void vChewingLM::setPhraseReplacementEnabled(bool enabled) +{ + m_phraseReplacementEnabled = enabled; +} + +bool vChewingLM::phraseReplacementEnabled() +{ + return m_phraseReplacementEnabled; +} diff --git a/Source/Engine/LanguageModel/vChewingLM.h b/Source/Engine/LanguageModel/vChewingLM.h index 0d57fcea6..cb42249a1 100644 --- a/Source/Engine/LanguageModel/vChewingLM.h +++ b/Source/Engine/LanguageModel/vChewingLM.h @@ -40,6 +40,7 @@ #include #include "FastLM.h" #include "UserPhrasesLM.h" +#include "PhraseReplacementMap.h" namespace vChewing { @@ -51,17 +52,23 @@ public: ~vChewingLM(); void loadLanguageModel(const char* languageModelDataPath); - void loadUserPhrases(const char* m_userPhrasesDataPath, - const char* m_excludedPhrasesDataPath); + void loadUserPhrases(const char* userPhrasesDataPath, + const char* excludedPhrasesDataPath); + void loadPhraseReplacementMap(const char* phraseReplacementPath); const vector bigramsForKeys(const string& preceedingKey, const string& key); const vector unigramsForKey(const string& key); bool hasUnigramsForKey(const string& key); + void setPhraseReplacementEnabled(bool enabled); + bool phraseReplacementEnabled(); + protected: FastLM m_languageModel; UserPhrasesLM m_userPhrases; UserPhrasesLM m_excludedPhrases; + PhraseReplacementMap m_phraseReplacement; + bool m_phraseReplacementEnabled; }; }; diff --git a/Source/Engine/vChewing/clsOOBEDefaults.swift b/Source/Engine/vChewing/clsOOBEDefaults.swift index 56975c8bd..8bc8fc95b 100644 --- a/Source/Engine/vChewing/clsOOBEDefaults.swift +++ b/Source/Engine/vChewing/clsOOBEDefaults.swift @@ -40,6 +40,7 @@ private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" private let kSelectPhraseAfterCursorAsCandidate = "SelectPhraseAfterCursorAsCandidate" private let kUseHorizontalCandidateList = "UseHorizontalCandidateList" private let kChineseConversionEnabledKey = "ChineseConversionEnabled" +private let kPhraseReplacementEnabledKey = "PhraseReplacementEnabled" @objc public class OOBE : NSObject { @@ -76,6 +77,11 @@ private let kChineseConversionEnabledKey = "ChineseConversionEnabled" if UserDefaults.standard.object(forKey: kChineseConversionEnabledKey) == nil { UserDefaults.standard.set(Preferences.chineseConversionEnabled, forKey: kChineseConversionEnabledKey) } + + // 預設停用自訂語彙置換 + if UserDefaults.standard.object(forKey: kPhraseReplacementEnabledKey) == nil { + UserDefaults.standard.set(Preferences.phraseReplacementEnabled, forKey: kPhraseReplacementEnabledKey) + } // 預設沒事不要在那裡放屁 if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil { diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 7582fdcfe..3a2b90172 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -140,6 +140,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) // create the lattice builder _languageModel = [LanguageModelManager languageModelBopomofo]; + _languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); _userOverrideModel = [LanguageModelManager userOverrideModel]; _builder = new BlockReadingBuilder(_languageModel); @@ -158,22 +159,28 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (NSMenu *)menu { + // Define the case which ALT / Option key is pressed. + BOOL optionKeyPressed = [[NSEvent class] respondsToSelector:@selector(modifierFlags)] && ([NSEvent modifierFlags] & NSAlternateKeyMask); + // a menu instance (autoreleased) is requested every time the user click on the input menu NSMenu *menu = [[NSMenu alloc] initWithTitle:LocalizationNotNeeded(@"Input Method Menu")]; [menu addItemWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; - NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; + NSMenuItem *chineseConversionMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; - [menu addItem:chineseConversionMenuItem]; - NSMenuItem *halfWidthPunctuationMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""]; + NSMenuItem *halfWidthPunctuationMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""]; halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; - [menu addItem:halfWidthPunctuationMenuItem]; + + if (optionKeyPressed) { + NSMenuItem *phaseReplacementMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Phrase Replacement", @"") action:@selector(togglePhraseReplacementEnabled:) keyEquivalent:@""]; + phaseReplacementMenuItem.state = Preferences.phraseReplacementEnabled ? NSControlStateValueOn : NSControlStateValueOff; + } [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ - + if (_inputMode == kSimpBopomofoModeIdentifier) { NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesSimpBopomofo:) keyEquivalent:@""]; [menu addItem:editExcludedPhrasesItem]; @@ -181,9 +188,12 @@ static double FindHighestScore(const vector& nodes, double epsilon) else { [menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; [menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesvChewing:) keyEquivalent:@""]; + if (optionKeyPressed) { + [menu addItemWithTitle:NSLocalizedString(@"Edit Phrase Replacement Table", @"") action:@selector(openPhraseReplacementvChewing:) keyEquivalent:@""]; + } } [menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; - + [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ [menu addItemWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; @@ -273,6 +283,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) else { newInputMode = kBopomofoModeIdentifier; newLanguageModel = [LanguageModelManager languageModelBopomofo]; + newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); } // Only apply the changes if the value is changed @@ -1486,6 +1497,27 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } +- (void)toggleChineseConverter:(id)sender +{ + BOOL chineseConversionEnabled = [Preferences toggleChineseConversionEnabled]; + [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; +} + +- (void)toggleHalfWidthPunctuation:(id)sender +{ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-result" + [Preferences toogleHalfWidthPunctuationEnabled]; +#pragma GCC diagnostic pop +} + +- (void)togglePhraseReplacementEnabled:(id)sender +{ + BOOL enabled = [Preferences tooglePhraseReplacementEnabled]; + vChewingLM *lm = [LanguageModelManager languageModelBopomofo]; + lm->setPhraseReplacementEnabled(enabled); +} + - (void)checkForUpdate:(id)sender { [(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES]; @@ -1526,9 +1558,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathBopomofo]]; } +- (void)openPhraseReplacementvChewing:(id)sender +{ + [self _openUserFile:[LanguageModelManager phraseReplacementDataPathBopomofo]]; +} + - (void)reloadUserPhrases:(id)sender { - [LanguageModelManager loadUserPhrasesModel]; + [LanguageModelManager loadUserPhrases]; + [LanguageModelManager loadUserPhraseReplacement]; } - (void)showAbout:(id)sender @@ -1538,23 +1576,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } -- (void)toggleChineseConverter:(id)sender -{ - BOOL chineseConversionEnabled = [Preferences toggleChineseConversionEnabled]; - [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; -} - -- (void)toggleHalfWidthPunctuation:(id)sender -{ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-result" - [Preferences toogleHalfWidthPunctuationEnabled]; -#pragma GCC diagnostic pop -} - @end -#pragma mark - +#pragma mark - Voltaire @implementation vChewingInputMethodController (VTCandidateController) diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index be4476a35..4fa02a927 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -44,7 +44,8 @@ NS_ASSUME_NONNULL_BEGIN @interface LanguageModelManager : NSObject + (void)loadDataModels; -+ (void)loadUserPhrasesModel; ++ (void)loadUserPhrases; ++ (void)loadUserPhraseReplacement; + (BOOL)checkIfUserLanguageModelFilesExist; + (BOOL)writeUserPhrase:(NSString *)userPhrase; @@ -52,6 +53,7 @@ NS_ASSUME_NONNULL_BEGIN @property (class, readonly, nonatomic) NSString *userPhrasesDataPathBopomofo; @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathBopomofo; @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathSimpBopomofo; +@property (class, readonly, nonatomic) NSString *phraseReplacementDataPathBopomofo; @property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelBopomofo; @property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelSimpBopomofo; @property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModel; diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 9ceed6c16..7e2ecd1d8 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -49,7 +49,7 @@ using namespace OpenVanilla; static const int kUserOverrideModelCapacity = 500; static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. -vChewingLM glanguageModelBopomofo; +vChewingLM gLanguageModelBopomofo; vChewingLM gLanguageModelSimpBopomofo; UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); @@ -64,16 +64,21 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (void)loadDataModels { - LTLoadLanguageModelFile(@"data", glanguageModelBopomofo); + LTLoadLanguageModelFile(@"data", gLanguageModelBopomofo); LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelSimpBopomofo); } -+ (void)loadUserPhrasesModel ++ (void)loadUserPhrases { - glanguageModelBopomofo.loadUserPhrases([[self userPhrasesDataPathBopomofo] UTF8String], [[self excludedPhrasesDataPathBopomofo] UTF8String]); + gLanguageModelBopomofo.loadUserPhrases([[self userPhrasesDataPathBopomofo] UTF8String], [[self excludedPhrasesDataPathBopomofo] UTF8String]); gLanguageModelSimpBopomofo.loadUserPhrases(NULL, [[self excludedPhrasesDataPathSimpBopomofo] UTF8String]); } ++ (void)loadUserPhraseReplacement +{ + gLanguageModelBopomofo.loadPhraseReplacementMap([[self phraseReplacementDataPathBopomofo] UTF8String]); +} + + (BOOL)checkIfUserDataFolderExists { NSString *folderPath = [self dataFolderPath]; @@ -125,6 +130,9 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing if (![self checkIfFileExist:[self excludedPhrasesDataPathSimpBopomofo]]) { return NO; } + if (![self checkIfFileExist:[self phraseReplacementDataPathBopomofo]]) { + return NO; + } return YES; } @@ -171,7 +179,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing [writeFile writeData:data]; [writeFile closeFile]; - [self loadUserPhrasesModel]; + [self loadUserPhrases]; return YES; } @@ -198,9 +206,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"]; } ++ (NSString *)phraseReplacementDataPathBopomofo +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"phrases-replacement.txt"]; +} + + (vChewingLM *)languageModelBopomofo { - return &glanguageModelBopomofo; + return &gLanguageModelBopomofo; } + (vChewingLM *)languageModelSimpBopomofo diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 1413218be..39ea0b4d2 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -51,6 +51,7 @@ private let kCandidateTextFontName = "CandidateTextFontName" private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" private let kCandidateKeys = "CandidateKeys" private let kChineseConversionEngineKey = "ChineseConversionEngine" +private let kPhraseReplacementEnabledKey = "PhraseReplacementEnabled" private let kDefaultCandidateListTextSize: CGFloat = 18 private let kMinKeyLabelSize: CGFloat = 10 @@ -297,4 +298,12 @@ struct ComposingKeys { return ChineseConversionEngine(rawValue: chineseConversionEngine)?.name } + @UserDefault(key: kPhraseReplacementEnabledKey, defaultValue: false) + @objc static var phraseReplacementEnabled: Bool + + @objc static func tooglePhraseReplacementEnabled() -> Bool { + phraseReplacementEnabled = !phraseReplacementEnabled + UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabledKey) + return phraseReplacementEnabled; + } } diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index c4f74b437..426b919f9 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -27,3 +27,5 @@ "Use Half-Width Punctuations" = "Use Half-Width Punctuations"; "You are now selecting \"%@\". You can add a phrase with two or more characters." = "You are now selecting \"%@\". You can add a phrase with two or more characters."; "You are now selecting \"%@\". Press enter to add a new phrase." = "You are now selecting \"%@\". Press enter to add a new phrase."; +"Edit Phrase Replacement Table" = "Edit Phrase Replacement Table"; +"Use Phrase Replacement" = "Use Phrase Replacement"; diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index e4c62d3f5..18ec5050b 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -6,5 +6,6 @@ #import // @import Foundation; @interface LanguageModelManager : NSObject + (void)loadDataModels; -+ (void)loadUserPhrasesModel; ++ (void)loadUserPhrases; ++ (void)loadUserPhraseReplacement; @end diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 8f7bae555..a3710edb7 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -27,3 +27,5 @@ "Use Half-Width Punctuations" = "啟用半角標點輸出"; "You are now selecting \"%@\". You can add a phrase with two or more characters." = "您目前选择了「%@」。请选择至少两个字,才能将其加入自订语汇。"; "You are now selecting \"%@\". Press enter to add a new phrase." = "您目前选择了「%@」。按下 Enter 就可以加入到自订语汇中。"; +"Edit Phrase Replacement Table" = "编辑语汇置换表"; +"Use Phrase Replacement" = "使用语汇置换"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index bdc29ed13..eec7b5144 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -27,3 +27,5 @@ "Use Half-Width Punctuations" = "啟用半形標點輸出"; "You are now selecting \"%@\". You can add a phrase with two or more characters." = "您目前選擇了「%@」。請選擇至少兩個字,才能將其加入自訂語彙。"; "You are now selecting \"%@\". Press enter to add a new phrase." = "您目前選擇了「%@」。按下 Enter 就可以加入到自訂語彙中。"; +"Edit Phrase Replacement Table" = "編輯語彙置換表"; +"Use Phrase Replacement" = "使用語彙置換"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 34dcd2cf0..21f1f779e 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */; }; 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */; }; 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */; }; + 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */; }; 5BC2D2872793B434002C0BEC /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2D2852793B434002C0BEC /* CMakeLists.txt */; }; 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; }; @@ -27,6 +28,7 @@ 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D19927943D390008F48E /* clsSFX.swift */; }; 5BD0D19F279454F60008F48E /* Beep.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19E279454F60008F48E /* Beep.aif */; }; 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */; }; + 5BD13F482794F0A6000E429F /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */; }; 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; @@ -102,6 +104,8 @@ 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = ""; }; 5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPhrasesLM.h; sourceTree = ""; }; 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UserPhrasesLM.cpp; sourceTree = ""; }; + 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = ""; }; + 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = ""; }; 5B9781D32763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -270,6 +274,8 @@ 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */, 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */, 5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */, + 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */, + 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */, ); path = LanguageModel; sourceTree = ""; @@ -741,9 +747,11 @@ 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, + 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */, 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */, + 5BD13F482794F0A6000E429F /* PhraseReplacementMap.cpp in Sources */, 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, -- Gitee From 2681bd1510cde95e46945b28e8663c336394a435 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 17 Jan 2022 19:42:17 +0800 Subject: [PATCH 061/163] Data update & Data format update --- Source/LanguageModelManager.mm | 6 +++--- vChewing.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 7e2ecd1d8..340b22eb2 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -64,8 +64,8 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (void)loadDataModels { - LTLoadLanguageModelFile(@"data", gLanguageModelBopomofo); - LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelSimpBopomofo); + LTLoadLanguageModelFile(@"data-cht", gLanguageModelBopomofo); + LTLoadLanguageModelFile(@"data-chs", gLanguageModelSimpBopomofo); } + (void)loadUserPhrases @@ -193,7 +193,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (NSString *)userPhrasesDataPathBopomofo { - return [[self dataFolderPath] stringByAppendingPathComponent:@"data.txt"]; + return [[self dataFolderPath] stringByAppendingPathComponent:@"data-cht.txt"]; } + (NSString *)excludedPhrasesDataPathBopomofo diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 21f1f779e..621f65a73 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -53,7 +53,7 @@ 6A225A232367A1D700F685C6 /* ArchiveUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A225A222367A1D700F685C6 /* ArchiveUtil.m */; }; 6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; 6A2E40F9253A6AA000D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; }; - 6A38BC1515FC117A00A8A51F /* data.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data.txt */; }; + 6A38BC1515FC117A00A8A51F /* data-cht.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6A38BBF615FC117A00A8A51F /* data-cht.txt */; }; 6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */; }; 6ACA41CD15FC1D7500935EF6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; 6ACA41F915FC1D9000935EF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41E915FC1D9000935EF6 /* AppDelegate.m */; }; @@ -223,7 +223,7 @@ 6A38BBEF15FC117A00A8A51F /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = ""; }; 6A38BBF015FC117A00A8A51F /* typocorrection.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = typocorrection.bash; sourceTree = ""; }; 6A38BBF115FC117A00A8A51F /* utf8length.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = utf8length.pl; sourceTree = ""; }; - 6A38BBF615FC117A00A8A51F /* data.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = data.txt; sourceTree = ""; }; + 6A38BBF615FC117A00A8A51F /* data-cht.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-cht.txt"; sourceTree = ""; }; 6A38BBFA15FC117A00A8A51F /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/InputMethodKit.framework; sourceTree = DEVELOPER_DIR; }; 6ACA41CB15FC1D7500935EF6 /* vChewingInstaller.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewingInstaller.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -514,7 +514,7 @@ isa = PBXGroup; children = ( 6A38BBE115FC117A00A8A51F /* bin */, - 6A38BBF615FC117A00A8A51F /* data.txt */, + 6A38BBF615FC117A00A8A51F /* data-cht.txt */, 5BC3FB82278492DE0022E99A /* data-chs.txt */, 6A38BBFA15FC117A00A8A51F /* Makefile */, ); @@ -567,7 +567,7 @@ /* Begin PBXLegacyTarget section */ 6A38BC2115FC12FD00A8A51F /* Data */ = { isa = PBXLegacyTarget; - buildArgumentsString = "$(ACTION) tsi-chs tsi-cht"; + buildArgumentsString = "$(ACTION) macv"; buildConfigurationList = 6A38BC2215FC12FD00A8A51F /* Build configuration list for PBXLegacyTarget "Data" */; buildPhases = ( ); -- Gitee From 445b01f60988d8a3844b64b8038f1c60570ace0c Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 17 Jan 2022 19:57:01 +0800 Subject: [PATCH 062/163] Zonble: Typing Alphabetic Contents to the Buffer. --- Source/Engine/LanguageModel/vChewingLM.cpp | 4 +++ Source/InputMethodController.mm | 35 ++++++++++++++-------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/Source/Engine/LanguageModel/vChewingLM.cpp b/Source/Engine/LanguageModel/vChewingLM.cpp index 3cdbf94ef..154f0ddbe 100644 --- a/Source/Engine/LanguageModel/vChewingLM.cpp +++ b/Source/Engine/LanguageModel/vChewingLM.cpp @@ -154,6 +154,10 @@ const vector vChewingLM::unigramsForKey(const string& key) bool vChewingLM::hasUnigramsForKey(const string& key) { + if (key == " ") { + return true; + } + if (!m_excludedPhrases.hasUnigramsForKey(key)) { return m_userPhrases.hasUnigramsForKey(key) || m_languageModel.hasUnigramsForKey(key); diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 3a2b90172..0555f7d5f 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -160,7 +160,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (NSMenu *)menu { // Define the case which ALT / Option key is pressed. - BOOL optionKeyPressed = [[NSEvent class] respondsToSelector:@selector(modifierFlags)] && ([NSEvent modifierFlags] & NSAlternateKeyMask); + BOOL optionKeyPressed = [[NSEvent class] respondsToSelector:@selector(modifierFlags)] && ([NSEvent modifierFlags] & NSEventModifierFlagOption); // a menu instance (autoreleased) is requested every time the user click on the input menu NSMenu *menu = [[NSMenu alloc] initWithTitle:LocalizationNotNeeded(@"Input Method Menu")]; @@ -550,10 +550,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it if (![_composingBuffer length] && - _bpmfReadingBuffer->isEmpty() && - ((flags & NSCommandKeyMask) || (flags & NSControlKeyMask) || (flags & NSAlternateKeyMask) || (flags & NSNumericPadKeyMask))) { - return NO; - } + _bpmfReadingBuffer->isEmpty() && + ((flags & NSEventModifierFlagCommand) || (flags & NSEventModifierFlagControl) || (flags & NSEventModifierFlagOption) || (flags & NSEventModifierFlagNumericPad))) { + return NO; + } // Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo. if (charCode == 8 || charCode == 13 || keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey || keyCode == cursorForwardKey || keyCode == cursorBackwardKey) { @@ -566,7 +566,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // first commit everything in the buffer. - if (flags & NSShiftKeyMask) { + if (flags & NSEventModifierFlagShift) { return NO; } @@ -581,7 +581,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } - if (flags & NSNumericPadKeyMask) { + if (flags & NSEventModifierFlagNumericPad) { if (keyCode != kLeftKeyCode && keyCode != kRightKeyCode && keyCode != kDownKeyCode && keyCode != kUpKeyCode && charCode != 32 && isprint(charCode)) { if ([_composingBuffer length]) { [self commitComposition:client]; @@ -619,7 +619,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // Shift + left if ((keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) - && (flags & NSShiftKeyMask)) { + && (flags & NSEventModifierFlagShift)) { if (_builder->markerCursorIndex() > 0) { _builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1); } @@ -631,7 +631,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } // Shift + Right if ((keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) - && (flags & NSShiftKeyMask)) { + && (flags & NSEventModifierFlagShift)) { if (_builder->markerCursorIndex() < _builder->length()) { _builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1); } @@ -706,7 +706,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (_bpmfReadingBuffer->isEmpty() && [_composingBuffer length] > 0 && (keyCode == extraChooseCandidateKey || charCode == 32 || (useVerticalMode && (keyCode == verticalModeOnlyChooseCandidateKey)))) { if (charCode == 32) { // if the spacebar is NOT set to be a selection key - if (!Preferences.chooseCandidateUsingSpace) { + if ((flags & NSEventModifierFlagShift) != 0 || !Preferences.chooseCandidateUsingSpace) { if (_builder->cursorIndex() >= _builder->length()) { [_composingBuffer appendString:@" "]; [self commitComposition:client]; @@ -773,7 +773,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return NO; } - if (flags & NSShiftKeyMask) { + if (flags & NSEventModifierFlagShift) { // Shift + left if (_builder->cursorIndex() > 0) { _builder->setMarkerCursorIndex(_builder->cursorIndex() - 1); @@ -805,7 +805,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return NO; } - if (flags & NSShiftKeyMask) { + if (flags & NSEventModifierFlagShift) { // Shift + Right if (_builder->cursorIndex() < _builder->length()) { _builder->setMarkerCursorIndex(_builder->cursorIndex() + 1); @@ -962,6 +962,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } + if ((char)charCode >= 'A' && (char)charCode <= 'Z') { + if ([_composingBuffer length]) { + string letter = string("_letter_") + string(1, (char)charCode); + if ([self _handlePunctuation:letter usingVerticalMode:useVerticalMode client:client]) { + return YES; + } + } + } + // still nothing, then we update the composing buffer (some app has // strange behavior if we don't do this, "thinking" the key is not // actually consumed) @@ -1209,7 +1218,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // Function key pressed. BOOL includeShift = Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey; - if (([event modifierFlags] & ~NSShiftKeyMask) || (([event modifierFlags] & NSShiftKeyMask) && includeShift)) { + if (([event modifierFlags] & ~NSEventModifierFlagShift) || (([event modifierFlags] & NSEventModifierFlagShift) && includeShift)) { // Override the keyboard layout and let the OS do its thing [client overrideKeyboardWithKeyboardNamed:functionKeyKeyboardLayoutID]; return NO; -- Gitee From f0e9346060d4913eb26cd531bb8c9a4d2826f1fc Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 17 Jan 2022 21:22:58 +0800 Subject: [PATCH 063/163] Zonble: Flexible candidate amount per page. --- Source/PreferencesModule.swift | 89 +++++++++++++++--------- Source/PreferencesWindowController.swift | 34 ++++++--- Source/en.lproj/Localizable.strings | 6 ++ Source/zh-Hans.lproj/Localizable.strings | 6 ++ Source/zh-Hant.lproj/Localizable.strings | 6 ++ 5 files changed, 97 insertions(+), 44 deletions(-) diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 39ea0b4d2..6f8e2a474 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -144,36 +144,6 @@ struct ComposingBufferSize { } } -@propertyWrapper -struct ComposingKeys { - let key: String - let defaultValue: String? = kCandidateKeys - lazy var container: UserDefault = { - UserDefault(key: key, defaultValue: defaultValue) }() - - var wrappedValue: String? { - mutating get { - let value = container.wrappedValue - if let value = value { - if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - return nil - } - } - return value - } - set { - let value = newValue - if let value = value { - if value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - container.wrappedValue = nil - return - } - } - container.wrappedValue = value - } - } -} - // MARK: - @objc enum KeyboardLayout: Int { case standard @@ -284,12 +254,65 @@ struct ComposingKeys { @UserDefault(key: kCandidateKeyLabelFontName, defaultValue: nil) @objc static var candidateKeyLabelFontName: String? - @ComposingKeys(key: kCandidateKeys) - @objc static var candidateKeys: String? + @UserDefault(key: kCandidateKeys, defaultValue: kDefaultKeys) + @objc static var candidateKeys: String - @objc static var defaultKeys: String { + @objc static var defaultCandidateKeys: String { kDefaultKeys } + @objc static var suggestedCandidateKeys: [String] { + [kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"] + } + + static func validate(candidateKeys: String) throws { + let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.isEmpty { + throw CandidateKeyError.empty + } + if !trimmed.canBeConverted(to: .ascii) { + throw CandidateKeyError.invalidCharacters + } + if trimmed.contains(" ") { + throw CandidateKeyError.containSpace + } + if trimmed.count < 4 { + throw CandidateKeyError.tooShort + } + if trimmed.count > 15 { + throw CandidateKeyError.tooLong + } + let set = Set(Array(trimmed)) + if set.count != trimmed.count { + throw CandidateKeyError.duplicatedCharacters + } + } + + enum CandidateKeyError: Error, LocalizedError { + case empty + case invalidCharacters + case containSpace + case duplicatedCharacters + case tooShort + case tooLong + + var errorDescription: String? { + switch self { + case .empty: + return NSLocalizedString("Candidates keys cannot be empty.", comment: "") + case .invalidCharacters: + return NSLocalizedString("Candidate keys can only contain ASCII characters like alphanumerals.", comment: "") + case .containSpace: + return NSLocalizedString("Candidate keys cannot contain space.", comment: "") + case .duplicatedCharacters: + return NSLocalizedString("There should not be duplicated keys.", comment: "") + case .tooShort: + return NSLocalizedString("The length of your candidate keys can not be less than 4 characters.", comment: "") + case .tooLong: + return NSLocalizedString("The length of your candidate keys can not be larger than 15 characters.", comment: "") + } + } + + } @UserDefault(key: kChineseConversionEngineKey, defaultValue: 0) @objc static var chineseConversionEngine: Int diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index d05289bf8..39c6c9189 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -136,11 +136,11 @@ extension RangeReplaceableCollection where Element: Hashable { basisKeyboardLayoutButton.select(chosenItem ?? usKeyboardLayoutItem) selectionKeyComboBox.usesDataSource = false selectionKeyComboBox.removeAllItems() - selectionKeyComboBox.addItems(withObjectValues: [Preferences.defaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"]) + selectionKeyComboBox.addItems(withObjectValues: Preferences.suggestedCandidateKeys) - var candidateSelectionKeys = Preferences.candidateKeys ?? Preferences.defaultKeys + var candidateSelectionKeys = Preferences.candidateKeys if candidateSelectionKeys.isEmpty { - candidateSelectionKeys = Preferences.defaultKeys + candidateSelectionKeys = Preferences.defaultCandidateKeys } selectionKeyComboBox.stringValue = candidateSelectionKeys @@ -157,14 +157,26 @@ extension RangeReplaceableCollection where Element: Hashable { } @IBAction func changeSelectionKeyAction(_ sender: Any) { - let keys = (sender as AnyObject).stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).charDeDuplicate - if keys.count != 9 || !keys.canBeConverted(to: .ascii) { - selectionKeyComboBox.stringValue = Preferences.defaultKeys - Preferences.candidateKeys = Preferences.defaultKeys // 修正記錄:這裡千萬不能是 nil,否則會鬼打牆。 - clsSFX.beep() - return - } - + guard let keys = (sender as AnyObject).stringValue?.trimmingCharacters(in: .whitespacesAndNewlines).charDeDuplicate else { + return + } + do { + try Preferences.validate(candidateKeys: keys) + Preferences.candidateKeys = keys + } + catch Preferences.CandidateKeyError.empty { + selectionKeyComboBox.stringValue = Preferences.candidateKeys + } + catch { + if let window = window { + let alert = NSAlert(error: error) + alert.beginSheetModal(for: window) { response in + self.selectionKeyComboBox.stringValue = Preferences.candidateKeys + } + clsSFX.beep() + } + } + selectionKeyComboBox.stringValue = keys Preferences.candidateKeys = keys } diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 426b919f9..0504f283a 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -29,3 +29,9 @@ "You are now selecting \"%@\". Press enter to add a new phrase." = "You are now selecting \"%@\". Press enter to add a new phrase."; "Edit Phrase Replacement Table" = "Edit Phrase Replacement Table"; "Use Phrase Replacement" = "Use Phrase Replacement"; +"Candidates keys cannot be empty." = "Candidates keys cannot be empty."; +"Candidate keys can only contain ASCII characters like alphanumerals." = "Candidate keys can only contain ASCII characters like alphanumerals."; +"Candidate keys cannot contain space." = "Candidate keys cannot contain space."; +"There should not be duplicated keys." = "There should not be duplicated keys."; +"The length of your candidate keys can not be less than 4 characters." = "The length of your candidate keys can not be less than 4 characters."; +"The length of your candidate keys can not be larger than 15 characters." = "The length of your candidate keys can not be larger than 15 characters."; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index a3710edb7..66ee79966 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -29,3 +29,9 @@ "You are now selecting \"%@\". Press enter to add a new phrase." = "您目前选择了「%@」。按下 Enter 就可以加入到自订语汇中。"; "Edit Phrase Replacement Table" = "编辑语汇置换表"; "Use Phrase Replacement" = "使用语汇置换"; +"Candidates keys cannot be empty." = "您必须指定选字键。"; +"Candidate keys can only contain ASCII characters like alphanumerals." = "选字键只能是英数等 ASCII 字符。"; +"Candidate keys cannot contain space." = "选字键不得包含空格。"; +"There should not be duplicated keys." = "选字键不得重复。"; +"The length of your candidate keys can not be less than 4 characters." = "请至少指定四个选字键。"; +"The length of your candidate keys can not be larger than 15 characters." = "选字键最多只能指定十五个。"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index eec7b5144..b25c23564 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -29,3 +29,9 @@ "You are now selecting \"%@\". Press enter to add a new phrase." = "您目前選擇了「%@」。按下 Enter 就可以加入到自訂語彙中。"; "Edit Phrase Replacement Table" = "編輯語彙置換表"; "Use Phrase Replacement" = "使用語彙置換"; +"Candidates keys cannot be empty." = "您必須指定選字鍵。"; +"Candidate keys can only contain ASCII characters like alphanumerals." = "選字鍵只能是英數等 ASCII 字符。"; +"Candidate keys cannot contain space." = "選字鍵不得包含空格。"; +"There should not be duplicated keys." = "選字鍵不得重複。"; +"The length of your candidate keys can not be less than 4 characters." = "請至少指定四個選字鍵。"; +"The length of your candidate keys can not be larger than 15 characters." = "選字鍵最多只能指定十五個。"; -- Gitee From bb46b8422b339b39b2f6ec42c5e1e52fbb34e3a5 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 17 Jan 2022 22:08:55 +0800 Subject: [PATCH 064/163] Change License to 3-Clause BSD License --- LICENSE-CHS.txt | 17 +-- LICENSE-CHT.txt | 17 +-- LICENSE.txt | 12 +- Source/AppDelegate.swift | 44 +------ Source/Base.lproj/frmAboutWindow.xib | 121 +++++++++--------- Source/Engine/Gramambular/Bigram.h | 33 +---- .../Engine/Gramambular/BlockReadingBuilder.h | 33 +---- Source/Engine/Gramambular/Gramambular.h | 33 +---- Source/Engine/Gramambular/Grid.h | 33 +---- Source/Engine/Gramambular/KeyValuePair.h | 33 +---- Source/Engine/Gramambular/LanguageModel.h | 33 +---- Source/Engine/Gramambular/Node.h | 33 +---- Source/Engine/Gramambular/NodeAnchor.h | 33 +---- Source/Engine/Gramambular/Span.h | 33 +---- Source/Engine/Gramambular/Unigram.h | 33 +---- Source/Engine/Gramambular/Walker.h | 33 +---- Source/Engine/Keyboard/EmacsKeyHelper.swift | 39 +----- Source/Engine/LanguageModel/FastLM.cpp | 33 +---- Source/Engine/LanguageModel/FastLM.h | 33 +---- .../LanguageModel/PhraseReplacementMap.cpp | 36 +----- .../LanguageModel/PhraseReplacementMap.h | 36 +----- .../LanguageModel/UserOverrideModel.cpp | 39 +----- .../Engine/LanguageModel/UserOverrideModel.h | 39 +----- Source/Engine/LanguageModel/UserPhrasesLM.cpp | 37 +----- Source/Engine/LanguageModel/UserPhrasesLM.h | 38 +----- Source/Engine/LanguageModel/vChewingLM.cpp | 42 +----- Source/Engine/LanguageModel/vChewingLM.h | 42 +----- Source/Engine/Mandarin/Mandarin.cpp | 33 +---- Source/Engine/Mandarin/Mandarin.h | 33 +---- Source/Engine/OpenVanilla/OVAroundFilter.h | 33 +---- Source/Engine/OpenVanilla/OVBase.h | 33 +---- Source/Engine/OpenVanilla/OVBenchmark.h | 33 +---- Source/Engine/OpenVanilla/OVCINDataTable.h | 33 +---- .../Engine/OpenVanilla/OVCINDatabaseService.h | 33 +---- .../OpenVanilla/OVCINToSQLiteConvertor.h | 33 +---- .../Engine/OpenVanilla/OVCandidateService.h | 33 +---- Source/Engine/OpenVanilla/OVDatabaseService.h | 33 +---- Source/Engine/OpenVanilla/OVDateTimeHelper.h | 33 +---- Source/Engine/OpenVanilla/OVEncodingService.h | 33 +---- .../OpenVanilla/OVEventHandlingContext.h | 33 +---- Source/Engine/OpenVanilla/OVException.h | 33 +---- Source/Engine/OpenVanilla/OVFileHelper.h | 33 +---- Source/Engine/OpenVanilla/OVFrameworkInfo.h | 33 +---- Source/Engine/OpenVanilla/OVInputMethod.h | 33 +---- Source/Engine/OpenVanilla/OVKey.h | 33 +---- Source/Engine/OpenVanilla/OVKeyPreprocessor.h | 33 +---- Source/Engine/OpenVanilla/OVKeyValueMap.h | 33 +---- Source/Engine/OpenVanilla/OVLoaderBase.h | 33 +---- Source/Engine/OpenVanilla/OVLoaderService.h | 33 +---- Source/Engine/OpenVanilla/OVLocalization.h | 33 +---- Source/Engine/OpenVanilla/OVModule.h | 33 +---- Source/Engine/OpenVanilla/OVModulePackage.h | 33 +---- Source/Engine/OpenVanilla/OVOutputFilter.h | 33 +---- Source/Engine/OpenVanilla/OVPathInfo.h | 33 +---- .../OpenVanilla/OVSQLiteDatabaseService.h | 33 +---- Source/Engine/OpenVanilla/OVSQLiteWrapper.h | 33 +---- Source/Engine/OpenVanilla/OVStringHelper.h | 33 +---- Source/Engine/OpenVanilla/OVTextBuffer.h | 33 +---- Source/Engine/OpenVanilla/OVUTF8Helper.h | 33 +---- Source/Engine/OpenVanilla/OVWildcard.h | 33 +---- Source/Engine/OpenVanilla/OpenVanilla.h | 33 +---- Source/Engine/SFX/clsSFX.swift | 40 +----- Source/Engine/vChewing/KeyValueBlobReader.cpp | 36 +----- Source/Engine/vChewing/KeyValueBlobReader.h | 36 +----- Source/Engine/vChewing/clsOOBEDefaults.swift | 39 +----- Source/InputMethodController.h | 44 +------ Source/InputMethodController.mm | 48 ++----- Source/InputSourceHelper.swift | 41 +----- Source/Installer/AppDelegate.h | 38 +----- Source/Installer/AppDelegate.m | 38 +----- Source/Installer/ArchiveUtil.h | 29 +---- Source/Installer/ArchiveUtil.m | 29 +---- Source/Installer/Base.lproj/MainMenu.xib | 38 +++--- Source/Installer/en.lproj/InfoPlist.strings | 2 +- Source/Installer/en.lproj/MainMenu.strings | 8 +- Source/Installer/main.m | 33 +---- .../Installer/zh-Hans.lproj/InfoPlist.strings | 2 +- .../Installer/zh-Hans.lproj/MainMenu.strings | 10 +- .../Installer/zh-Hant.lproj/InfoPlist.strings | 2 +- .../Installer/zh-Hant.lproj/MainMenu.strings | 10 +- Source/LanguageModelManager.h | 42 +----- Source/LanguageModelManager.mm | 42 +----- Source/NonModalAlertWindowController.swift | 40 +----- Source/OpenCCBridge.swift | 39 +----- Source/PreferencesModule.swift | 39 +----- Source/PreferencesWindowController.swift | 41 +----- Source/Tools/genRTF.py | 8 ++ Source/Tools/tistool.m | 33 +---- .../HorizontalCandidateController.swift | 45 +------ .../CandidateUI/VTCandidateController.swift | 42 +----- .../VerticalCandidateController.swift | 45 +------ Source/UI/NotifierUI/NotifierController.swift | 41 +----- Source/UI/TooltipUI/TooltipController.swift | 41 +----- Source/en.lproj/BSDLicense.txt | 11 ++ Source/en.lproj/InfoPlist.strings | 6 +- Source/en.lproj/MITLicense.txt | 9 -- Source/en.lproj/frmAboutWindow.strings | 8 +- Source/frmAboutWindow.h | 32 +---- Source/frmAboutWindow.m | 32 +---- Source/main.m | 44 +------ Source/vChewing-Bridging-Header.h | 8 ++ Source/vChewing-Info.plist | 14 +- Source/vChewingInstaller-Bridging-Header.h | 8 ++ Source/zh-Hans.lproj/BSDLicense.txt | 9 ++ Source/zh-Hans.lproj/InfoPlist.strings | 6 +- Source/zh-Hans.lproj/MITLicense.txt | 9 -- Source/zh-Hans.lproj/frmAboutWindow.strings | 10 +- Source/zh-Hant.lproj/BSDLicense.txt | 9 ++ Source/zh-Hant.lproj/InfoPlist.strings | 6 +- Source/zh-Hant.lproj/MITLicense.txt | 9 -- Source/zh-Hant.lproj/frmAboutWindow.strings | 10 +- vChewing.xcodeproj/project.pbxproj | 28 ++-- 112 files changed, 809 insertions(+), 2590 deletions(-) create mode 100644 Source/en.lproj/BSDLicense.txt delete mode 100644 Source/en.lproj/MITLicense.txt create mode 100644 Source/zh-Hans.lproj/BSDLicense.txt delete mode 100644 Source/zh-Hans.lproj/MITLicense.txt create mode 100644 Source/zh-Hant.lproj/BSDLicense.txt delete mode 100644 Source/zh-Hant.lproj/MITLicense.txt diff --git a/LICENSE-CHS.txt b/LICENSE-CHS.txt index 09248e092..967e25b89 100644 --- a/LICENSE-CHS.txt +++ b/LICENSE-CHS.txt @@ -1,13 +1,14 @@ -vChewing macOS: MIT-NTL License 麻理(去商标)授权合约 +vChewing macOS: 3-Clause BSD License 伯克利软件三款型授权合约 © 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project. -小麦注音引擎研发:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang (Zonble), 等。 -威注音 macOS 程式研发协力:Hiraku Wang。\n威注音词库维护:孙志贵 (Shiki Suen)。 +小麦注音引擎研发:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, 等。 +威注音 macOS 程式研发协力:Hiraku Wang。 +威注音词库维护:孙志贵 (Shiki Suen)。 -软件之著作权利人依此麻理授权条款,将其对于软件之著作权利授权释出,只须使用者践履以下二项麻理授权条款叙明之义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用之权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面之应用,而散布程式之人、更可将上述权利传递予其后收受程式之后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款之义务性规定,则其对程式亦享有与前手运用范围相同之同一权利。 +该授权条款,在使用者符合以下诸条件之情形下,授予使用者使用及再散播本软件套件装原始码及二进位可执行形式之权利,无论此包装是否经改作皆然: -甲、散布此一软件程序者,须将本条款其上之「著作权声明」及以下之「免责声明」内嵌于软件程序及其重制作品之实体之中。 +1)对于本软件原始码之再散播,必须保留上述之著作权宣告、此诸条件表列,以及下述之免责声明。 +2)对于本套件二进位可执行形式之再散播,必须连带以档案以及/或者其他附于散播包装中之媒介方式,重制上述之著作权宣告、此诸条件表列,以及下述之免责声明。 +3)未获事前取得书面授权,不得使用威注音或本软件贡献者之名称,来为本软件之衍生物做任何表示支援、认可或推广、促销之行为。 -乙、敝授权合约不提供对「贡献者」之商品名称、商标、服务标志或产品名称之商标许可,除非用以满足履行上文所述义务之必要。 - -因麻理软件程序之授权模式乃是无偿提供,是以在现行法律之架构下可以主张合理之免除担保责任。麻理软件之著作权人或任何之后续散布者,对于其所散布之麻理软件程序皆不负任何形式上实质上之担保责任,明示亦或隐喻、商业利用性亦或特定目之使用性,这些均不在保障之列。利用麻理软件程序之所有风险均由使用者自行担负。假如所使用之麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要之服务支出。麻理软件程序之著作权人不负任何形式上实质上之担保责任,无论任何一般之、特殊之、偶发之、因果关系式之损害,或是麻理软件程序之不适用性,均须由使用者自行负担。 +免责声明:本软件由威注音及本软件之贡献者以现状(「as is」)提供,本软件套件装不负任何明示或默示之担保责任,包括但不限于就适售性以及特定目之之适用性为默示性担保。威注音及本软件之贡献者,无论任何条件、无论成因或任何责任主义、无论此责任为因合约关系、无过失责任主义或因非违约之侵权(包括过失或其他原因等)而起,对于任何因使用本软件套件装所产生之任何直接性、间接性、偶发性、特殊性、惩罚性或任何结果之损害(包括但不限于替代商品或劳务之购用、使用损失、资料损失、利益损失、业务中断等等),不负任何责任,即在该种使用已获事前告知可能会造成此类损害之情形下亦然。 diff --git a/LICENSE-CHT.txt b/LICENSE-CHT.txt index 1a7f68662..3f605f19b 100644 --- a/LICENSE-CHT.txt +++ b/LICENSE-CHT.txt @@ -1,13 +1,14 @@ -vChewing macOS: MIT-NTL License 麻理(去商標)授權合約 +vChewing macOS: 3-Clause BSD License 柏克萊軟體三款型授權合約 © 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project. -小麥注音引擎研發:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang (Zonble), 等。 -威注音 macOS 程式研發協力:Hiraku Wang。\n威注音詞庫維護:孫志貴 (Shiki Suen)。 +小麥注音引擎研發:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, 等。 +威注音 macOS 程式研發協力:Hiraku Wang。 +威注音詞庫維護:孫志貴 (Shiki Suen)。 -軟體之著作權利人依此麻理授權條款,將其對於軟體之著作權利授權釋出,只須使用者踐履以下二項麻理授權條款敘明之義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用之權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面之應用,而散布程式之人、更可將上述權利傳遞予其後收受程式之後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款之義務性規定,則其對程式亦享有與前手運用範圍相同之同一權利。 +該授權條款,在使用者符合以下諸條件之情形下,授予使用者使用及再散播本軟體套件裝原始碼及二進位可執行形式之權利,無論此包裝是否經改作皆然: -甲、散布此一軟體程式者,須將本條款其上之「著作權聲明」及以下之「免責聲明」內嵌於軟體程式及其重製作品之實體之中。 +1)對於本軟體原始碼之再散播,必須保留上述之著作權宣告、此諸條件表列,以及下述之免責聲明。 +2)對於本套件二進位可執行形式之再散播,必須連帶以檔案以及/或者其他附於散播包裝中之媒介方式,重製上述之著作權宣告、此諸條件表列,以及下述之免責聲明。 +3)未獲事前取得書面授權,不得使用威注音或本軟體貢獻者之名稱,來為本軟體之衍生物做任何表示支援、認可或推廣、促銷之行為。 -乙、敝授權合約不提供對「貢獻者」之商品名稱、商標、服務標誌或產品名稱之商標許可,除非用以滿足履行上文所述義務之必要。 - -因麻理軟體程式之授權模式乃是無償提供,是以在現行法律之架構下可以主張合理之免除擔保責任。麻理軟體之著作權人或任何之後續散布者,對於其所散布之麻理軟體程式皆不負任何形式上實質上之擔保責任,明示亦或隱喻、商業利用性亦或特定目之使用性,這些均不在保障之列。利用麻理軟體程式之所有風險均由使用者自行擔負。假如所使用之麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要之服務支出。麻理軟體程式之著作權人不負任何形式上實質上之擔保責任,無論任何一般之、特殊之、偶發之、因果關係式之損害,或是麻理軟體程式之不適用性,均須由使用者自行負擔。 +免責聲明:本軟體由威注音及本軟體之貢獻者以現狀(「as is」)提供,本軟體套件裝不負任何明示或默示之擔保責任,包括但不限於就適售性以及特定目之之適用性為默示性擔保。威注音及本軟體之貢獻者,無論任何條件、無論成因或任何責任主義、無論此責任為因合約關係、無過失責任主義或因非違約之侵權(包括過失或其他原因等)而起,對於任何因使用本軟體套件裝所產生之任何直接性、間接性、偶發性、特殊性、懲罰性或任何結果之損害(包括但不限於替代商品或勞務之購用、使用損失、資料損失、利益損失、業務中斷等等),不負任何責任,即在該種使用已獲事前告知可能會造成此類損害之情形下亦然。 diff --git a/LICENSE.txt b/LICENSE.txt index 284cf9ae3..e7fd4b4d4 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,14 +1,16 @@ -vChewing macOS: MIT-NTL License +vChewing macOS: 3-Clause BSD License © 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project. McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al. vChewing macOS Development Reinforced by Hiraku Wang. 
vChewing Phrase Database Maintained by Shiki Suen. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 49a539eaa..342857e0f 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -1,40 +1,10 @@ -// -// AppDelegate.swift -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla -// Weizhong Yang (@zonble) @ OpenVanilla -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Hiraku Wang (@hirakujira) @ vChewing -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * AppDelegate.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ import Cocoa import InputMethodKit diff --git a/Source/Base.lproj/frmAboutWindow.xib b/Source/Base.lproj/frmAboutWindow.xib index c2834bf05..12d4811f0 100644 --- a/Source/Base.lproj/frmAboutWindow.xib +++ b/Source/Base.lproj/frmAboutWindow.xib @@ -18,27 +18,27 @@ - + - - + + - + - + - + - + - - + + @@ -57,12 +57,12 @@ - + - + @@ -70,7 +70,7 @@ - + @@ -78,9 +78,9 @@ - + - + @@ -89,7 +89,7 @@ - + McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al. @@ -100,20 +100,20 @@ vChewing Phrase Database Maintained by Shiki Suen. - + - - + + - + - + - DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database. + DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database. - + - + - + - + @@ -158,39 +158,40 @@ DQ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Engine/Gramambular/Bigram.h b/Source/Engine/Gramambular/Bigram.h index 42ac9033b..a02f52938 100644 --- a/Source/Engine/Gramambular/Bigram.h +++ b/Source/Engine/Gramambular/Bigram.h @@ -1,29 +1,10 @@ -// -// Bigram.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * Bigram.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef Bigram_h #define Bigram_h diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index a8bf87eea..8186bcf7f 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -1,29 +1,10 @@ -// -// BlockReadingBuilder.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * BlockReadingBuilder.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef BlockReadingBuilder_h #define BlockReadingBuilder_h diff --git a/Source/Engine/Gramambular/Gramambular.h b/Source/Engine/Gramambular/Gramambular.h index 1036ff70e..3123dd46f 100644 --- a/Source/Engine/Gramambular/Gramambular.h +++ b/Source/Engine/Gramambular/Gramambular.h @@ -1,29 +1,10 @@ -// -// Gramambular.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * Gramambular.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef Gramambular_h #define Gramambular_h diff --git a/Source/Engine/Gramambular/Grid.h b/Source/Engine/Gramambular/Grid.h index 770263793..06c754ed1 100644 --- a/Source/Engine/Gramambular/Grid.h +++ b/Source/Engine/Gramambular/Grid.h @@ -1,29 +1,10 @@ -// -// Grid.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * Grid.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef Grid_h #define Grid_h diff --git a/Source/Engine/Gramambular/KeyValuePair.h b/Source/Engine/Gramambular/KeyValuePair.h index 0abbb8911..b9e540946 100644 --- a/Source/Engine/Gramambular/KeyValuePair.h +++ b/Source/Engine/Gramambular/KeyValuePair.h @@ -1,29 +1,10 @@ -// -// KeyValuePair.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * KeyValuePair.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef KeyValuePair_h #define KeyValuePair_h diff --git a/Source/Engine/Gramambular/LanguageModel.h b/Source/Engine/Gramambular/LanguageModel.h index 65331b37b..8370b435a 100644 --- a/Source/Engine/Gramambular/LanguageModel.h +++ b/Source/Engine/Gramambular/LanguageModel.h @@ -1,29 +1,10 @@ -// -// LanguageModel.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * LanguageModel.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef LanguageModel_h #define LanguageModel_h diff --git a/Source/Engine/Gramambular/Node.h b/Source/Engine/Gramambular/Node.h index f265ebe91..7d574cbf1 100644 --- a/Source/Engine/Gramambular/Node.h +++ b/Source/Engine/Gramambular/Node.h @@ -1,29 +1,10 @@ -// -// Node.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * Node.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef Node_h #define Node_h diff --git a/Source/Engine/Gramambular/NodeAnchor.h b/Source/Engine/Gramambular/NodeAnchor.h index 62e5e12ef..aa8553106 100644 --- a/Source/Engine/Gramambular/NodeAnchor.h +++ b/Source/Engine/Gramambular/NodeAnchor.h @@ -1,29 +1,10 @@ -// -// NodeAnchor.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * NodeAnchor.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef NodeAnchor_h #define NodeAnchor_h diff --git a/Source/Engine/Gramambular/Span.h b/Source/Engine/Gramambular/Span.h index 87cb65638..fdaf8f9f1 100644 --- a/Source/Engine/Gramambular/Span.h +++ b/Source/Engine/Gramambular/Span.h @@ -1,29 +1,10 @@ -// -// Span.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * Span.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef Span_h #define Span_h diff --git a/Source/Engine/Gramambular/Unigram.h b/Source/Engine/Gramambular/Unigram.h index 5af285021..283083d46 100644 --- a/Source/Engine/Gramambular/Unigram.h +++ b/Source/Engine/Gramambular/Unigram.h @@ -1,29 +1,10 @@ -// -// Unigram.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * Unigram.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef Unigram_h #define Unigram_h diff --git a/Source/Engine/Gramambular/Walker.h b/Source/Engine/Gramambular/Walker.h index c40ffbf19..ea09d61e5 100644 --- a/Source/Engine/Gramambular/Walker.h +++ b/Source/Engine/Gramambular/Walker.h @@ -1,29 +1,10 @@ -// -// Walker.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * Walker.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef Walker_h #define Walker_h diff --git a/Source/Engine/Keyboard/EmacsKeyHelper.swift b/Source/Engine/Keyboard/EmacsKeyHelper.swift index b5001f4d6..45e2818bf 100644 --- a/Source/Engine/Keyboard/EmacsKeyHelper.swift +++ b/Source/Engine/Keyboard/EmacsKeyHelper.swift @@ -1,35 +1,10 @@ -// -// EmacsKeyHelper.swift -// -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Weizhong Yang (@zonble) @ OpenVanilla -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * EmacsKeyHelper.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ import Cocoa diff --git a/Source/Engine/LanguageModel/FastLM.cpp b/Source/Engine/LanguageModel/FastLM.cpp index dc5423370..f68675624 100644 --- a/Source/Engine/LanguageModel/FastLM.cpp +++ b/Source/Engine/LanguageModel/FastLM.cpp @@ -1,29 +1,10 @@ -// -// FastLM.h: A fast unigram language model for Gramambular -// -// Copyright (c) 2012 Lukhnos Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * FastLM.cpp + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #include "FastLM.h" #include diff --git a/Source/Engine/LanguageModel/FastLM.h b/Source/Engine/LanguageModel/FastLM.h index 69fb8bd9d..d0a6e9c19 100644 --- a/Source/Engine/LanguageModel/FastLM.h +++ b/Source/Engine/LanguageModel/FastLM.h @@ -1,29 +1,10 @@ -// -// FastLM.h: A fast unigram language model for Gramambular -// -// Copyright (c) 2012 Lukhnos Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * FastLM.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef FASTLM_H #define FASTLM_H diff --git a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp index 06c8bf88e..fb6900cc0 100644 --- a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp @@ -1,32 +1,10 @@ -// -// PhraseReplacementMap.cpp -// -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Weizhong Yang (@zonble) @ OpenVanilla -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * PhraseReplacementMap.cpp + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #include #include diff --git a/Source/Engine/LanguageModel/PhraseReplacementMap.h b/Source/Engine/LanguageModel/PhraseReplacementMap.h index b775fc502..3ac864b71 100644 --- a/Source/Engine/LanguageModel/PhraseReplacementMap.h +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.h @@ -1,32 +1,10 @@ -// -// PhraseReplacementMap.h -// -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Weizhong Yang (@zonble) @ OpenVanilla -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * PhraseReplacementMap.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef PHRASEREPLACEMENTMAP_H #define PHRASEREPLACEMENTMAP_H diff --git a/Source/Engine/LanguageModel/UserOverrideModel.cpp b/Source/Engine/LanguageModel/UserOverrideModel.cpp index 37b222cf9..36d62d9dc 100644 --- a/Source/Engine/LanguageModel/UserOverrideModel.cpp +++ b/Source/Engine/LanguageModel/UserOverrideModel.cpp @@ -1,35 +1,10 @@ -// -// UserOverrideModel.cpp -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Weizhong Yang (@zonble) @ OpenVanilla -// Hiraku Wang (@hirakujira) @ vChewing -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * UserOverrideModel.cpp + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #include "UserOverrideModel.h" diff --git a/Source/Engine/LanguageModel/UserOverrideModel.h b/Source/Engine/LanguageModel/UserOverrideModel.h index 3159e74eb..6268a9de6 100644 --- a/Source/Engine/LanguageModel/UserOverrideModel.h +++ b/Source/Engine/LanguageModel/UserOverrideModel.h @@ -1,35 +1,10 @@ -// -// UserOverrideModel.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Weizhong Yang (@zonble) @ OpenVanilla -// Hiraku Wang (@hirakujira) @ vChewing -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * UserOverrideModel.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef USEROVERRIDEMODEL_H #define USEROVERRIDEMODEL_H diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.cpp b/Source/Engine/LanguageModel/UserPhrasesLM.cpp index 548f002e1..be7264c23 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.cpp +++ b/Source/Engine/LanguageModel/UserPhrasesLM.cpp @@ -1,33 +1,10 @@ -// -// UserPhraseLM.cpp -// -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Weizhong Yang (@zonble) @ OpenVanilla -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * UserPhrasesLM.cpp + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #include "UserPhrasesLM.h" diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.h b/Source/Engine/LanguageModel/UserPhrasesLM.h index 53f93ced5..e010b720f 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.h +++ b/Source/Engine/LanguageModel/UserPhrasesLM.h @@ -1,34 +1,10 @@ -// -// UserPhraseLM.h -// -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Weizhong Yang (@zonble) @ OpenVanilla -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - +/* + * UserPhrasesLM.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef USERPHRASESLM_H #define USERPHRASESLM_H diff --git a/Source/Engine/LanguageModel/vChewingLM.cpp b/Source/Engine/LanguageModel/vChewingLM.cpp index 154f0ddbe..f5b15a2ea 100644 --- a/Source/Engine/LanguageModel/vChewingLM.cpp +++ b/Source/Engine/LanguageModel/vChewingLM.cpp @@ -1,38 +1,10 @@ -// -// vChewingLM.cpp -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Weizhong Yang (@zonble) @ OpenVanilla -// Hiraku Wang (@hirakujira) @ vChewing -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * vChewingLM.cpp + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #include "vChewingLM.h" #include diff --git a/Source/Engine/LanguageModel/vChewingLM.h b/Source/Engine/LanguageModel/vChewingLM.h index cb42249a1..bd44efde9 100644 --- a/Source/Engine/LanguageModel/vChewingLM.h +++ b/Source/Engine/LanguageModel/vChewingLM.h @@ -1,38 +1,10 @@ -// -// vChewingLM.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Weizhong Yang (@zonble) @ OpenVanilla -// Hiraku Wang (@hirakujira) @ vChewing -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * vChewingLM.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef VCHEWINGLM_H #define VCHEWINGLM_H diff --git a/Source/Engine/Mandarin/Mandarin.cpp b/Source/Engine/Mandarin/Mandarin.cpp index 2fa0452b7..58c4b35df 100644 --- a/Source/Engine/Mandarin/Mandarin.cpp +++ b/Source/Engine/Mandarin/Mandarin.cpp @@ -1,29 +1,10 @@ -// -// Mandarin.cpp -// -// Copyright (c) 2006-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * Mandarin.cpp + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #include #include diff --git a/Source/Engine/Mandarin/Mandarin.h b/Source/Engine/Mandarin/Mandarin.h index 01ede5898..48e4e9d6d 100644 --- a/Source/Engine/Mandarin/Mandarin.h +++ b/Source/Engine/Mandarin/Mandarin.h @@ -1,29 +1,10 @@ -// -// Mandarin.h -// -// Copyright (c) 2006-2010 Lukhnos D. Liu (http://lukhnos.org) -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * Mandarin.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef Mandarin_h #define Mandarin_h diff --git a/Source/Engine/OpenVanilla/OVAroundFilter.h b/Source/Engine/OpenVanilla/OVAroundFilter.h index dc024f30e..efd076cc3 100644 --- a/Source/Engine/OpenVanilla/OVAroundFilter.h +++ b/Source/Engine/OpenVanilla/OVAroundFilter.h @@ -1,29 +1,10 @@ -// -// OVAroundFilter.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVAroundFilter.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVAroundFilter_h #define OVAroundFilter_h diff --git a/Source/Engine/OpenVanilla/OVBase.h b/Source/Engine/OpenVanilla/OVBase.h index aa4c9a398..dd3b3d391 100644 --- a/Source/Engine/OpenVanilla/OVBase.h +++ b/Source/Engine/OpenVanilla/OVBase.h @@ -1,29 +1,10 @@ -// -// OVBase.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVBase.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVBase_h #define OVBase_h diff --git a/Source/Engine/OpenVanilla/OVBenchmark.h b/Source/Engine/OpenVanilla/OVBenchmark.h index f37f625ac..fff5363f3 100644 --- a/Source/Engine/OpenVanilla/OVBenchmark.h +++ b/Source/Engine/OpenVanilla/OVBenchmark.h @@ -1,29 +1,10 @@ -// -// OVBenchmark.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVBenchmark.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVBenchmark_h #define OVBenchmark_h diff --git a/Source/Engine/OpenVanilla/OVCINDataTable.h b/Source/Engine/OpenVanilla/OVCINDataTable.h index 3638b7687..99261f916 100644 --- a/Source/Engine/OpenVanilla/OVCINDataTable.h +++ b/Source/Engine/OpenVanilla/OVCINDataTable.h @@ -1,29 +1,10 @@ -// -// OVCINDataTable.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVCINDataTable.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVCINDataTable_h #define OVCINDataTable_h diff --git a/Source/Engine/OpenVanilla/OVCINDatabaseService.h b/Source/Engine/OpenVanilla/OVCINDatabaseService.h index 8ff666cb6..691d60aa2 100644 --- a/Source/Engine/OpenVanilla/OVCINDatabaseService.h +++ b/Source/Engine/OpenVanilla/OVCINDatabaseService.h @@ -1,29 +1,10 @@ -// -// OVCINDatabaseService.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVCINDatabaseService.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVCINDatabaseService_h #define OVCINDatabaseService_h diff --git a/Source/Engine/OpenVanilla/OVCINToSQLiteConvertor.h b/Source/Engine/OpenVanilla/OVCINToSQLiteConvertor.h index 4a73d6b3d..22e543a05 100644 --- a/Source/Engine/OpenVanilla/OVCINToSQLiteConvertor.h +++ b/Source/Engine/OpenVanilla/OVCINToSQLiteConvertor.h @@ -1,29 +1,10 @@ -// -// OVCINToSQLiteConvertor.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVCINToSQLiteConvertor.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVCINToSQLiteConvertor_h #define OVCINToSQLiteConvertor_h diff --git a/Source/Engine/OpenVanilla/OVCandidateService.h b/Source/Engine/OpenVanilla/OVCandidateService.h index ecaff8c95..e756b8c5b 100644 --- a/Source/Engine/OpenVanilla/OVCandidateService.h +++ b/Source/Engine/OpenVanilla/OVCandidateService.h @@ -1,29 +1,10 @@ -// -// OVCandidateService.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVCandidateService.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVCandidateService_h #define OVCandidateService_h diff --git a/Source/Engine/OpenVanilla/OVDatabaseService.h b/Source/Engine/OpenVanilla/OVDatabaseService.h index 6baf5e050..bc4974c4d 100644 --- a/Source/Engine/OpenVanilla/OVDatabaseService.h +++ b/Source/Engine/OpenVanilla/OVDatabaseService.h @@ -1,29 +1,10 @@ -// -// OVDatabaseService.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVDatabaseService.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVDatabaseService_h #define OVDatabaseService_h diff --git a/Source/Engine/OpenVanilla/OVDateTimeHelper.h b/Source/Engine/OpenVanilla/OVDateTimeHelper.h index 117800ea6..8a2845b9d 100644 --- a/Source/Engine/OpenVanilla/OVDateTimeHelper.h +++ b/Source/Engine/OpenVanilla/OVDateTimeHelper.h @@ -1,29 +1,10 @@ -// -// OVDateTimeHelper.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVDateTimeHelper.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVDateTimeHelper_h #define OVDateTimeHelper_h diff --git a/Source/Engine/OpenVanilla/OVEncodingService.h b/Source/Engine/OpenVanilla/OVEncodingService.h index 4afd0e4bd..2ccf67a6a 100644 --- a/Source/Engine/OpenVanilla/OVEncodingService.h +++ b/Source/Engine/OpenVanilla/OVEncodingService.h @@ -1,29 +1,10 @@ -// -// OVEncodingService.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVEncodingService.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVEncodingService_h #define OVEncodingService_h diff --git a/Source/Engine/OpenVanilla/OVEventHandlingContext.h b/Source/Engine/OpenVanilla/OVEventHandlingContext.h index 51c12c8b9..098e29ea3 100644 --- a/Source/Engine/OpenVanilla/OVEventHandlingContext.h +++ b/Source/Engine/OpenVanilla/OVEventHandlingContext.h @@ -1,29 +1,10 @@ -// -// OVEventHandlingContext.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVEventHandlingContext.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVEventHandlingContext_h #define OVEventHandlingContext_h diff --git a/Source/Engine/OpenVanilla/OVException.h b/Source/Engine/OpenVanilla/OVException.h index 27b9fb73b..5de3ac153 100644 --- a/Source/Engine/OpenVanilla/OVException.h +++ b/Source/Engine/OpenVanilla/OVException.h @@ -1,29 +1,10 @@ -// -// OVException.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVException.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVException_h #define OVException_h diff --git a/Source/Engine/OpenVanilla/OVFileHelper.h b/Source/Engine/OpenVanilla/OVFileHelper.h index 3899674a4..715bf1c53 100644 --- a/Source/Engine/OpenVanilla/OVFileHelper.h +++ b/Source/Engine/OpenVanilla/OVFileHelper.h @@ -1,29 +1,10 @@ -// -// OVFileHelper.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVFileHelper.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVFileHelper_h #define OVFileHelper_h diff --git a/Source/Engine/OpenVanilla/OVFrameworkInfo.h b/Source/Engine/OpenVanilla/OVFrameworkInfo.h index 700706356..e03455d5b 100644 --- a/Source/Engine/OpenVanilla/OVFrameworkInfo.h +++ b/Source/Engine/OpenVanilla/OVFrameworkInfo.h @@ -1,29 +1,10 @@ -// -// OVFrameworkInfo.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVFrameworkInfo.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVFrameworkVersion_h #define OVFrameworkVersion_h diff --git a/Source/Engine/OpenVanilla/OVInputMethod.h b/Source/Engine/OpenVanilla/OVInputMethod.h index 0f4c68e65..4bdaff260 100644 --- a/Source/Engine/OpenVanilla/OVInputMethod.h +++ b/Source/Engine/OpenVanilla/OVInputMethod.h @@ -1,29 +1,10 @@ -// -// OVInputMethod.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVInputMethod.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVInputMethod_h #define OVInputMethod_h diff --git a/Source/Engine/OpenVanilla/OVKey.h b/Source/Engine/OpenVanilla/OVKey.h index 222344700..083bbb97c 100644 --- a/Source/Engine/OpenVanilla/OVKey.h +++ b/Source/Engine/OpenVanilla/OVKey.h @@ -1,29 +1,10 @@ -// -// OVKey.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu and Weizhong Yang -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVKey.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVKey_h #define OVKey_h diff --git a/Source/Engine/OpenVanilla/OVKeyPreprocessor.h b/Source/Engine/OpenVanilla/OVKeyPreprocessor.h index e82e3d2cd..2fee40428 100644 --- a/Source/Engine/OpenVanilla/OVKeyPreprocessor.h +++ b/Source/Engine/OpenVanilla/OVKeyPreprocessor.h @@ -1,29 +1,10 @@ -// -// OVKeyPreprocessor.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVKeyPreprocessor.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVKeyPreprocessor_h #define OVKeyPreprocessor_h diff --git a/Source/Engine/OpenVanilla/OVKeyValueMap.h b/Source/Engine/OpenVanilla/OVKeyValueMap.h index 2d18f2208..e520569aa 100644 --- a/Source/Engine/OpenVanilla/OVKeyValueMap.h +++ b/Source/Engine/OpenVanilla/OVKeyValueMap.h @@ -1,29 +1,10 @@ -// -// OVKeyValueMap.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVKeyValueMap.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVKeyValueMap_h #define OVKeyValueMap_h diff --git a/Source/Engine/OpenVanilla/OVLoaderBase.h b/Source/Engine/OpenVanilla/OVLoaderBase.h index fdaef0a05..a4cea581f 100644 --- a/Source/Engine/OpenVanilla/OVLoaderBase.h +++ b/Source/Engine/OpenVanilla/OVLoaderBase.h @@ -1,29 +1,10 @@ -// -// OVLoaderBase.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVLoaderBase.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVLoaderBase_h #define OVLoaderBase_h diff --git a/Source/Engine/OpenVanilla/OVLoaderService.h b/Source/Engine/OpenVanilla/OVLoaderService.h index c0994e0a5..371547763 100644 --- a/Source/Engine/OpenVanilla/OVLoaderService.h +++ b/Source/Engine/OpenVanilla/OVLoaderService.h @@ -1,29 +1,10 @@ -// -// OVLoaderService.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVLoaderService.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVLoaderService_h #define OVLoaderService_h diff --git a/Source/Engine/OpenVanilla/OVLocalization.h b/Source/Engine/OpenVanilla/OVLocalization.h index e02bb08d3..d220fa4e6 100644 --- a/Source/Engine/OpenVanilla/OVLocalization.h +++ b/Source/Engine/OpenVanilla/OVLocalization.h @@ -1,29 +1,10 @@ -// -// OVLocalization.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVLocalization.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVLocalization_h #define OVLocalization_h diff --git a/Source/Engine/OpenVanilla/OVModule.h b/Source/Engine/OpenVanilla/OVModule.h index 4694aee95..fc9ea31f4 100644 --- a/Source/Engine/OpenVanilla/OVModule.h +++ b/Source/Engine/OpenVanilla/OVModule.h @@ -1,29 +1,10 @@ -// -// OVModule.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVModule.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVModule_h #define OVModule_h diff --git a/Source/Engine/OpenVanilla/OVModulePackage.h b/Source/Engine/OpenVanilla/OVModulePackage.h index 21c980807..5110306ef 100644 --- a/Source/Engine/OpenVanilla/OVModulePackage.h +++ b/Source/Engine/OpenVanilla/OVModulePackage.h @@ -1,29 +1,10 @@ -// -// OVModulePackage.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVModulePackage.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVModulePackage_h #define OVModulePackage_h diff --git a/Source/Engine/OpenVanilla/OVOutputFilter.h b/Source/Engine/OpenVanilla/OVOutputFilter.h index 5477b9dc6..1ec4f8208 100644 --- a/Source/Engine/OpenVanilla/OVOutputFilter.h +++ b/Source/Engine/OpenVanilla/OVOutputFilter.h @@ -1,29 +1,10 @@ -// -// OVOutputFilter.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVOutputFilter.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVOutputFilter_h #define OVOutputFilter_h diff --git a/Source/Engine/OpenVanilla/OVPathInfo.h b/Source/Engine/OpenVanilla/OVPathInfo.h index 70eab5736..b49602c79 100644 --- a/Source/Engine/OpenVanilla/OVPathInfo.h +++ b/Source/Engine/OpenVanilla/OVPathInfo.h @@ -1,29 +1,10 @@ -// -// OVPathInfo.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVPathInfo.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVPathInfo_h #define OVPathInfo_h diff --git a/Source/Engine/OpenVanilla/OVSQLiteDatabaseService.h b/Source/Engine/OpenVanilla/OVSQLiteDatabaseService.h index a5b2333c1..8222a0ab8 100644 --- a/Source/Engine/OpenVanilla/OVSQLiteDatabaseService.h +++ b/Source/Engine/OpenVanilla/OVSQLiteDatabaseService.h @@ -1,29 +1,10 @@ -// -// OVSQLiteDatabaseService.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVSQLiteDatabaseService.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVSQLiteDatabaseService_h #define OVSQLiteDatabaseService_h diff --git a/Source/Engine/OpenVanilla/OVSQLiteWrapper.h b/Source/Engine/OpenVanilla/OVSQLiteWrapper.h index 1e056f92c..243226176 100644 --- a/Source/Engine/OpenVanilla/OVSQLiteWrapper.h +++ b/Source/Engine/OpenVanilla/OVSQLiteWrapper.h @@ -1,29 +1,10 @@ -// -// OVSQLiteWrapper.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVSQLiteWrapper.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVSQLite3Wrapper #define OVSQLite3Wrapper diff --git a/Source/Engine/OpenVanilla/OVStringHelper.h b/Source/Engine/OpenVanilla/OVStringHelper.h index 7e2b8e2b0..ee130e228 100644 --- a/Source/Engine/OpenVanilla/OVStringHelper.h +++ b/Source/Engine/OpenVanilla/OVStringHelper.h @@ -1,29 +1,10 @@ -// -// OVStringHelper.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVStringHelper.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVStringHelper_h #define OVStringHelper_h diff --git a/Source/Engine/OpenVanilla/OVTextBuffer.h b/Source/Engine/OpenVanilla/OVTextBuffer.h index 21300c854..8a7963391 100644 --- a/Source/Engine/OpenVanilla/OVTextBuffer.h +++ b/Source/Engine/OpenVanilla/OVTextBuffer.h @@ -1,29 +1,10 @@ -// -// OVTextBuffer.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVTextBuffer.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVTextBuffer_h #define OVTextBuffer_h diff --git a/Source/Engine/OpenVanilla/OVUTF8Helper.h b/Source/Engine/OpenVanilla/OVUTF8Helper.h index cd1e00494..51709e7d3 100644 --- a/Source/Engine/OpenVanilla/OVUTF8Helper.h +++ b/Source/Engine/OpenVanilla/OVUTF8Helper.h @@ -1,29 +1,10 @@ -// -// OVUTF8Helper.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVUTF8Helper.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVUTF8Helper_h #define OVUTF8Helper_h diff --git a/Source/Engine/OpenVanilla/OVWildcard.h b/Source/Engine/OpenVanilla/OVWildcard.h index 6a7906ce1..1da05ea0b 100644 --- a/Source/Engine/OpenVanilla/OVWildcard.h +++ b/Source/Engine/OpenVanilla/OVWildcard.h @@ -1,29 +1,10 @@ -// -// OVWildcard.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OVWildcard.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OVWildcard_h #define OVWildcard_h diff --git a/Source/Engine/OpenVanilla/OpenVanilla.h b/Source/Engine/OpenVanilla/OpenVanilla.h index c78271fd5..d6ed383a5 100644 --- a/Source/Engine/OpenVanilla/OpenVanilla.h +++ b/Source/Engine/OpenVanilla/OpenVanilla.h @@ -1,29 +1,10 @@ -// -// OpenVanilla.h -// -// Copyright (c) 2007-2010 Lukhnos D. Liu -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * OpenVanilla.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef OpenVanilla_h #define OpenVanilla_h diff --git a/Source/Engine/SFX/clsSFX.swift b/Source/Engine/SFX/clsSFX.swift index 1b184f760..a7b20df1c 100644 --- a/Source/Engine/SFX/clsSFX.swift +++ b/Source/Engine/SFX/clsSFX.swift @@ -1,36 +1,10 @@ -// -// clsSFX.swift -// -// Copyright (c) 2021-2022 The vChewing Project. -// -// Contributors: -// Shiki Suen (@ShikiSuen) @ vChewing -// Isaac Xen a.k.a. ix4n33 (@IsaacXen) @ no affiliation -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * clsSFX.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ import Cocoa diff --git a/Source/Engine/vChewing/KeyValueBlobReader.cpp b/Source/Engine/vChewing/KeyValueBlobReader.cpp index 3319c4c7e..2e3ac1524 100644 --- a/Source/Engine/vChewing/KeyValueBlobReader.cpp +++ b/Source/Engine/vChewing/KeyValueBlobReader.cpp @@ -1,32 +1,10 @@ -// -// KeyValueBlobReader.cpp -// -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * KeyValueBlobReader.cpp + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #include "KeyValueBlobReader.h" diff --git a/Source/Engine/vChewing/KeyValueBlobReader.h b/Source/Engine/vChewing/KeyValueBlobReader.h index e0991e778..d95ef3738 100644 --- a/Source/Engine/vChewing/KeyValueBlobReader.h +++ b/Source/Engine/vChewing/KeyValueBlobReader.h @@ -1,32 +1,10 @@ -// -// KeyValueBlobReader.h -// -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * KeyValueBlobReader.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #ifndef SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ #define SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ diff --git a/Source/Engine/vChewing/clsOOBEDefaults.swift b/Source/Engine/vChewing/clsOOBEDefaults.swift index 8bc8fc95b..95ddfff7b 100644 --- a/Source/Engine/vChewing/clsOOBEDefaults.swift +++ b/Source/Engine/vChewing/clsOOBEDefaults.swift @@ -1,35 +1,10 @@ -// -// clsOOBEDefaults.swift -// -// Copyright (c) 2021-2022 The vChewing Project. -// -// Contributors: -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * clsOOBEDefaults.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ import Cocoa diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index 75714bb52..f8148cd7b 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -1,40 +1,10 @@ -// -// InputMethodController.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla -// Weizhong Yang (@zonble) @ OpenVanilla -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Hiraku Wang (@hirakujira) @ vChewing -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * InputMethodController.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #import #import diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 0555f7d5f..fce56c1b7 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1,40 +1,10 @@ -// -// InputMethodController.m -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla -// Weizhong Yang (@zonble) @ OpenVanilla -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Hiraku Wang (@hirakujira) @ vChewing -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * InputMethodController.mm + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #import "InputMethodController.h" #import @@ -55,8 +25,8 @@ using namespace OpenVanilla; static const NSInteger kMinKeyLabelSize = 10; // input modes -static NSString *const kBopomofoModeIdentifier = @"org.openvanilla.inputmethod.vChewing.Bopomofo"; -static NSString *const kSimpBopomofoModeIdentifier = @"org.openvanilla.inputmethod.vChewing.SimpBopomofo"; +static NSString *const kBopomofoModeIdentifier = @"org.atelierInmu.inputmethod.vChewing.Bopomofo"; +static NSString *const kSimpBopomofoModeIdentifier = @"org.atelierInmu.inputmethod.vChewing.SimpBopomofo"; // key code enums enum { diff --git a/Source/InputSourceHelper.swift b/Source/InputSourceHelper.swift index 152b1b141..1715c25f5 100644 --- a/Source/InputSourceHelper.swift +++ b/Source/InputSourceHelper.swift @@ -1,37 +1,10 @@ -// -// InputSourceHelper.swift -// -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Mengjuei Hsieh (@mjhsieh) @ OpenVanilla -// Weizhong Yang (@zonble) @ OpenVanilla -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// -// Based on the Syrup Project and the Formosana Library -// by Lukhnos Liu (@lukhnos). -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * InputSourceHelper.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ import Cocoa import Carbon diff --git a/Source/Installer/AppDelegate.h b/Source/Installer/AppDelegate.h index f6a1da403..f70eb881a 100644 --- a/Source/Installer/AppDelegate.h +++ b/Source/Installer/AppDelegate.h @@ -1,34 +1,10 @@ -// -// AppDelegate.h -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * AppDelegate.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #import #import "ArchiveUtil.h" diff --git a/Source/Installer/AppDelegate.m b/Source/Installer/AppDelegate.m index 28cfa0cae..3bb180948 100644 --- a/Source/Installer/AppDelegate.m +++ b/Source/Installer/AppDelegate.m @@ -1,34 +1,10 @@ -// -// AppDelegate.m -// -// Copyright (c) 2021-2022 The vChewing Project. -// Copyright (c) 2011-2022 The OpenVanilla Project. -// -// Contributors: -// Lukhnos Liu (@lukhnos) @ OpenVanilla -// Shiki Suen (@ShikiSuen) @ vChewing -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// +/* + * AppDelegate.m + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #import "AppDelegate.h" #import diff --git a/Source/Installer/ArchiveUtil.h b/Source/Installer/ArchiveUtil.h index e6c38bc73..57a978cd6 100644 --- a/Source/Installer/ArchiveUtil.h +++ b/Source/Installer/ArchiveUtil.h @@ -1,25 +1,10 @@ -// Copyright (c) 2011-2019 The vChewing Project. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +/* + * ArchiveUtil.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #import diff --git a/Source/Installer/ArchiveUtil.m b/Source/Installer/ArchiveUtil.m index 278387326..118e9ca99 100644 --- a/Source/Installer/ArchiveUtil.m +++ b/Source/Installer/ArchiveUtil.m @@ -1,25 +1,10 @@ -// Copyright (c) 2011-2019 The vChewing Project. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +/* + * ArchiveUtil.m + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ #import "ArchiveUtil.h" diff --git a/Source/Installer/Base.lproj/MainMenu.xib b/Source/Installer/Base.lproj/MainMenu.xib index afe8440c6..8a7e82b26 100644 --- a/Source/Installer/Base.lproj/MainMenu.xib +++ b/Source/Installer/Base.lproj/MainMenu.xib @@ -66,16 +66,16 @@ - + - - + + - + - + @@ -123,7 +123,7 @@ - + @@ -131,7 +131,7 @@ - + @@ -139,7 +139,7 @@ - + @@ -150,7 +150,7 @@ - + McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al. @@ -160,37 +160,37 @@ vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database - + - - + + - + - DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database. + DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database. - + - + @@ -201,7 +201,7 @@ vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database - + @@ -209,7 +209,7 @@ vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database + + + + diff --git a/Source/Engine/Gramambular/Bigram.h b/Source/Engine/Gramambular/Bigram.h index a02f52938..db4b09d94 100644 --- a/Source/Engine/Gramambular/Bigram.h +++ b/Source/Engine/Gramambular/Bigram.h @@ -13,7 +13,7 @@ #include "KeyValuePair.h" -namespace Formosa { +namespace Taiyan { namespace Gramambular { class Bigram { public: diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index 8186bcf7f..b4c5bcc4e 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -13,7 +13,7 @@ #include "Grid.h" #include "LanguageModel.h" -namespace Formosa { +namespace Taiyan { namespace Gramambular { using namespace std; diff --git a/Source/Engine/Gramambular/Grid.h b/Source/Engine/Gramambular/Grid.h index 06c754ed1..e15095b21 100644 --- a/Source/Engine/Gramambular/Grid.h +++ b/Source/Engine/Gramambular/Grid.h @@ -13,7 +13,7 @@ #include "NodeAnchor.h" #include "Span.h" -namespace Formosa { +namespace Taiyan { namespace Gramambular { class Grid { diff --git a/Source/Engine/Gramambular/KeyValuePair.h b/Source/Engine/Gramambular/KeyValuePair.h index b9e540946..d2bf00d5e 100644 --- a/Source/Engine/Gramambular/KeyValuePair.h +++ b/Source/Engine/Gramambular/KeyValuePair.h @@ -12,7 +12,7 @@ #include #include -namespace Formosa { +namespace Taiyan { namespace Gramambular { using namespace std; diff --git a/Source/Engine/Gramambular/LanguageModel.h b/Source/Engine/Gramambular/LanguageModel.h index 8370b435a..c3f353361 100644 --- a/Source/Engine/Gramambular/LanguageModel.h +++ b/Source/Engine/Gramambular/LanguageModel.h @@ -13,7 +13,7 @@ #include "Bigram.h" #include "Unigram.h" -namespace Formosa { +namespace Taiyan { namespace Gramambular { using namespace std; diff --git a/Source/Engine/Gramambular/Node.h b/Source/Engine/Gramambular/Node.h index 7d574cbf1..1621ec322 100644 --- a/Source/Engine/Gramambular/Node.h +++ b/Source/Engine/Gramambular/Node.h @@ -13,7 +13,7 @@ #include #include "LanguageModel.h" -namespace Formosa { +namespace Taiyan { namespace Gramambular { using namespace std; diff --git a/Source/Engine/Gramambular/NodeAnchor.h b/Source/Engine/Gramambular/NodeAnchor.h index aa8553106..b17f2a4b9 100644 --- a/Source/Engine/Gramambular/NodeAnchor.h +++ b/Source/Engine/Gramambular/NodeAnchor.h @@ -11,7 +11,7 @@ #include "Node.h" -namespace Formosa { +namespace Taiyan { namespace Gramambular { class NodeAnchor { public: diff --git a/Source/Engine/Gramambular/Span.h b/Source/Engine/Gramambular/Span.h index fdaf8f9f1..5c520572b 100644 --- a/Source/Engine/Gramambular/Span.h +++ b/Source/Engine/Gramambular/Span.h @@ -14,7 +14,7 @@ #include #include "Node.h" -namespace Formosa { +namespace Taiyan { namespace Gramambular { class Span { public: diff --git a/Source/Engine/Gramambular/Unigram.h b/Source/Engine/Gramambular/Unigram.h index 283083d46..175406805 100644 --- a/Source/Engine/Gramambular/Unigram.h +++ b/Source/Engine/Gramambular/Unigram.h @@ -12,7 +12,7 @@ #include #include "KeyValuePair.h" -namespace Formosa { +namespace Taiyan { namespace Gramambular { class Unigram { public: diff --git a/Source/Engine/Gramambular/Walker.h b/Source/Engine/Gramambular/Walker.h index ea09d61e5..9dbbbf644 100644 --- a/Source/Engine/Gramambular/Walker.h +++ b/Source/Engine/Gramambular/Walker.h @@ -12,7 +12,7 @@ #include #include "Grid.h" -namespace Formosa { +namespace Taiyan { namespace Gramambular { using namespace std; diff --git a/Source/Engine/LanguageModel/FastLM.cpp b/Source/Engine/LanguageModel/FastLM.cpp index f68675624..bcfcf1940 100644 --- a/Source/Engine/LanguageModel/FastLM.cpp +++ b/Source/Engine/LanguageModel/FastLM.cpp @@ -14,7 +14,7 @@ #include #include -using namespace Formosa::Gramambular; +using namespace Taiyan::Gramambular; FastLM::FastLM() : fd(-1) diff --git a/Source/Engine/LanguageModel/FastLM.h b/Source/Engine/LanguageModel/FastLM.h index d0a6e9c19..aa94a005d 100644 --- a/Source/Engine/LanguageModel/FastLM.h +++ b/Source/Engine/LanguageModel/FastLM.h @@ -18,7 +18,7 @@ // format, and we use mmap and zero-out the separators and line feeds // to avoid creating new string objects; the parser is a simple DFA -namespace Formosa { +namespace Taiyan { namespace Gramambular { class FastLM : public LanguageModel { diff --git a/Source/Engine/LanguageModel/UserOverrideModel.h b/Source/Engine/LanguageModel/UserOverrideModel.h index 6268a9de6..7e2e2c099 100644 --- a/Source/Engine/LanguageModel/UserOverrideModel.h +++ b/Source/Engine/LanguageModel/UserOverrideModel.h @@ -17,7 +17,7 @@ namespace vChewing { -using namespace Formosa::Gramambular; +using namespace Taiyan::Gramambular; class UserOverrideModel { public: diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.cpp b/Source/Engine/LanguageModel/UserPhrasesLM.cpp index be7264c23..6d7ee6e1b 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.cpp +++ b/Source/Engine/LanguageModel/UserPhrasesLM.cpp @@ -94,19 +94,19 @@ void UserPhrasesLM::dump() } } -const std::vector UserPhrasesLM::bigramsForKeys(const std::string& preceedingKey, const std::string& key) +const std::vector UserPhrasesLM::bigramsForKeys(const std::string& preceedingKey, const std::string& key) { - return std::vector(); + return std::vector(); } -const std::vector UserPhrasesLM::unigramsForKey(const std::string& key) +const std::vector UserPhrasesLM::unigramsForKey(const std::string& key) { - std::vector v; + std::vector v; auto iter = keyRowMap.find(key); if (iter != keyRowMap.end()) { const std::vector& rows = iter->second; for (const auto& row : rows) { - Formosa::Gramambular::Unigram g; + Taiyan::Gramambular::Unigram g; g.keyValue.key = row.key; g.keyValue.value = row.value; g.score = 0.0; diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.h b/Source/Engine/LanguageModel/UserPhrasesLM.h index e010b720f..c5bfcbc26 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.h +++ b/Source/Engine/LanguageModel/UserPhrasesLM.h @@ -16,7 +16,7 @@ namespace vChewing { -class UserPhrasesLM : public Formosa::Gramambular::LanguageModel +class UserPhrasesLM : public Taiyan::Gramambular::LanguageModel { public: UserPhrasesLM(); @@ -26,8 +26,8 @@ public: void close(); void dump(); - virtual const std::vector bigramsForKeys(const std::string& preceedingKey, const std::string& key); - virtual const std::vector unigramsForKey(const std::string& key); + virtual const std::vector bigramsForKeys(const std::string& preceedingKey, const std::string& key); + virtual const std::vector unigramsForKey(const std::string& key); virtual bool hasUnigramsForKey(const std::string& key); protected: diff --git a/Source/Engine/LanguageModel/vChewingLM.h b/Source/Engine/LanguageModel/vChewingLM.h index bd44efde9..95bf0d09e 100644 --- a/Source/Engine/LanguageModel/vChewingLM.h +++ b/Source/Engine/LanguageModel/vChewingLM.h @@ -16,7 +16,7 @@ namespace vChewing { -using namespace Formosa::Gramambular; +using namespace Taiyan::Gramambular; class vChewingLM : public LanguageModel { public: diff --git a/Source/Engine/Mandarin/Mandarin.cpp b/Source/Engine/Mandarin/Mandarin.cpp index 58c4b35df..17ed259f8 100644 --- a/Source/Engine/Mandarin/Mandarin.cpp +++ b/Source/Engine/Mandarin/Mandarin.cpp @@ -13,7 +13,7 @@ #include "OVUTF8Helper.h" #include "OVWildcard.h" -namespace Formosa { +namespace Taiyan { namespace Mandarin { using namespace OpenVanilla; @@ -991,4 +991,4 @@ const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HanyuPinyinLayout() } // namespace Mandarin -} // namespace Formosa +} // namespace Taiyan diff --git a/Source/Engine/Mandarin/Mandarin.h b/Source/Engine/Mandarin/Mandarin.h index 48e4e9d6d..b60a4bb35 100644 --- a/Source/Engine/Mandarin/Mandarin.h +++ b/Source/Engine/Mandarin/Mandarin.h @@ -14,7 +14,7 @@ #include #include -namespace Formosa { +namespace Taiyan { namespace Mandarin { using namespace std; diff --git a/Source/Engine/vChewing/clsOOBEDefaults.swift b/Source/Engine/vChewing/clsOOBEDefaults.swift index 95ddfff7b..324cf3554 100644 --- a/Source/Engine/vChewing/clsOOBEDefaults.swift +++ b/Source/Engine/vChewing/clsOOBEDefaults.swift @@ -12,6 +12,7 @@ private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" private let kCheckUpdateAutomatically = "CheckUpdateAutomatically" private let kCandidateListTextSize = "CandidateListTextSize" private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" +private let kUseWinNT351BPMF = "UseWinNT351BPMF" private let kSelectPhraseAfterCursorAsCandidate = "SelectPhraseAfterCursorAsCandidate" private let kUseHorizontalCandidateList = "UseHorizontalCandidateList" private let kChineseConversionEnabledKey = "ChineseConversionEnabled" @@ -38,6 +39,11 @@ private let kPhraseReplacementEnabledKey = "PhraseReplacementEnabled" UserDefaults.standard.set(Preferences.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpaceKey) } + // 預設禁用 WinNT351 風格的注音選字模式(就是每個字都要選的那種),所以設成 false + if UserDefaults.standard.object(forKey: kUseWinNT351BPMF) == nil { + UserDefaults.standard.set(Preferences.useWinNT351BPMF, forKey: kUseWinNT351BPMF) + } + // 預設漢音風格選字,所以要設成 0 if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidate) == nil { UserDefaults.standard.set(Preferences.selectPhraseAfterCursorAsCandidate, forKey: kSelectPhraseAfterCursorAsCandidate) diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index f8148cd7b..ac0269d61 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -18,7 +18,7 @@ { @private // the reading buffer that takes user input - Formosa::Mandarin::BopomofoReadingBuffer* _bpmfReadingBuffer; + Taiyan::Mandarin::BopomofoReadingBuffer* _bpmfReadingBuffer; // language model vChewing::vChewingLM *_languageModel; @@ -27,10 +27,10 @@ vChewing::UserOverrideModel *_userOverrideModel; // the grid (lattice) builder for the unigrams (and bigrams) - Formosa::Gramambular::BlockReadingBuilder* _builder; + Taiyan::Gramambular::BlockReadingBuilder* _builder; // latest walked path (trellis) using the Viterbi algorithm - std::vector _walkedNodes; + std::vector _walkedNodes; // user override model vChewing::UserOverrideModel *_uom; diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 60c16cacb..44335a869 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -7,6 +7,7 @@ */ #import "InputMethodController.h" +#include #import #import #import @@ -17,8 +18,8 @@ // C++ namespace usages using namespace std; -using namespace Formosa::Mandarin; -using namespace Formosa::Gramambular; +using namespace Taiyan::Mandarin; +using namespace Taiyan::Gramambular; using namespace vChewing; using namespace OpenVanilla; @@ -109,7 +110,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) _bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout()); // create the lattice builder - _languageModel = [LanguageModelManager languageModelBopomofo]; + _languageModel = [LanguageModelManager languageModelCoreCHT]; _languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); _userOverrideModel = [LanguageModelManager userOverrideModel]; @@ -151,17 +152,12 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ - if (_inputMode == kBopomofoModeIdentifierCHS) { - NSMenuItem *editExcludedPhrasesItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesSimpBopomofo:) keyEquivalent:@""]; - [menu addItem:editExcludedPhrasesItem]; - } - else { - [menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; - [menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrasesvChewing:) keyEquivalent:@""]; - if (optionKeyPressed) { - [menu addItemWithTitle:NSLocalizedString(@"Edit Phrase Replacement Table", @"") action:@selector(openPhraseReplacementvChewing:) keyEquivalent:@""]; - } + [menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; + if (optionKeyPressed) { + [menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrases:) keyEquivalent:@""]; + [menu addItemWithTitle:NSLocalizedString(@"Edit Phrase Replacement Table", @"") action:@selector(openPhraseReplacement:) keyEquivalent:@""]; } + [menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ @@ -248,14 +244,15 @@ static double FindHighestScore(const vector& nodes, double epsilon) if ([value isKindOfClass:[NSString class]] && [value isEqual:kBopomofoModeIdentifierCHS]) { newInputMode = kBopomofoModeIdentifierCHS; - newLanguageModel = [LanguageModelManager languageModelSimpBopomofo]; - } - else { + newLanguageModel = [LanguageModelManager languageModelCoreCHS]; + } else { newInputMode = kBopomofoModeIdentifierCHT; - newLanguageModel = [LanguageModelManager languageModelBopomofo]; - newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); + newLanguageModel = [LanguageModelManager languageModelCoreCHT]; } + // 自 Preferences 模組讀入自訂語彙置換功能開關狀態。 + newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); + // Only apply the changes if the value is changed if (![_inputMode isEqualToString:newInputMode]) { [[NSUserDefaults standardUserDefaults] synchronize]; @@ -650,8 +647,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self popOverflowComposingTextAndWalk:client]; // get user override model suggestion - string overrideValue = (_inputMode == kBopomofoModeIdentifierCHS) ? "" : - _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + string overrideValue = _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); if (!overrideValue.empty()) { size_t cursorIndex = [self actualCandidateCursorIndex]; @@ -663,11 +659,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } // then update the text _bpmfReadingBuffer->clear(); [self updateClientComposingBuffer:client]; - - if (_inputMode == kBopomofoModeIdentifierCHS) { + + // 模擬 WINNT 351 ㄅ半注音,就是每個漢字都自動要選字的那種注音。 + // 嚴格來講不能算純正的ㄅ半注音,畢竟候選字的順序不可能會像當年那樣了。 + // 如果簡體中文用戶不知道ㄅ半注音是什麼的話,拿全拼輸入法來比喻恐怕比較恰當。 + if (Preferences.useWinNT351BPMF) { [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; } - + // and tells the client that the key is consumed return YES; } @@ -965,7 +964,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } [self updateClientComposingBuffer:client]; - if (_inputMode == kBopomofoModeIdentifierCHS && _bpmfReadingBuffer->isEmpty()) { + if (Preferences.useWinNT351BPMF && _bpmfReadingBuffer->isEmpty()) { [self collectCandidates]; if ([_candidates count] == 1) { [self commitComposition:client]; @@ -983,14 +982,14 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { BOOL cancelCandidateKey = (charCode == 27) || - ((_inputMode == kBopomofoModeIdentifierCHS) && + (Preferences.useWinNT351BPMF && (charCode == 8 || keyCode == kDeleteKeyCode)); if (cancelCandidateKey) { gCurrentCandidateController.visible = NO; [_candidates removeAllObjects]; - if (_inputMode == kBopomofoModeIdentifierCHS) { + if (Preferences.useWinNT351BPMF) { _builder->clear(); _walkedNodes.clear(); [_composingBuffer setString:@""]; @@ -1147,7 +1146,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } - if (_inputMode == kBopomofoModeIdentifierCHS) { + if (Preferences.useWinNT351BPMF) { string layout = [self _currentLayout]; string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); string punctuation = string("_punctuation_") + string(1, (char)charCode); @@ -1316,7 +1315,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } gCurrentCandidateController.keyLabels = keyLabels; [self collectCandidates]; - if (_inputMode == kBopomofoModeIdentifierCHS && [_candidates count] == 1) { + if (Preferences.useWinNT351BPMF && [_candidates count] == 1) { [self commitComposition:client]; return; } @@ -1493,7 +1492,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)togglePhraseReplacementEnabled:(id)sender { BOOL enabled = [Preferences tooglePhraseReplacementEnabled]; - vChewingLM *lm = [LanguageModelManager languageModelBopomofo]; + vChewingLM *lm = [LanguageModelManager languageModelCoreCHT]; lm->setPhraseReplacementEnabled(enabled); } @@ -1532,12 +1531,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathSimpBopomofo]]; } -- (void)openExcludedPhrasesvChewing:(id)sender +- (void)openExcludedPhrases:(id)sender { [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathBopomofo]]; } -- (void)openPhraseReplacementvChewing:(id)sender +- (void)openPhraseReplacement:(id)sender { [self _openUserFile:[LanguageModelManager phraseReplacementDataPathBopomofo]]; } @@ -1580,7 +1579,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } size_t cursorIndex = [self actualCandidateCursorIndex]; _builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue); - if (_inputMode != kBopomofoModeIdentifierCHS) { + if (!Preferences.useWinNT351BPMF) { _userOverrideModel->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); } @@ -1589,7 +1588,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [self walk]; [self updateClientComposingBuffer:_currentCandidateClient]; - if (_inputMode == kBopomofoModeIdentifierCHS) { + if (Preferences.useWinNT351BPMF) { [self commitComposition:_currentCandidateClient]; return; } diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index 9fd939a25..aa9a6f044 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -26,8 +26,8 @@ NS_ASSUME_NONNULL_BEGIN @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathBopomofo; @property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathSimpBopomofo; @property (class, readonly, nonatomic) NSString *phraseReplacementDataPathBopomofo; -@property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelBopomofo; -@property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelSimpBopomofo; +@property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelCoreCHT; +@property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelCoreCHS; @property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModel; @end diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 1c6d93a4f..da9688173 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -14,15 +14,15 @@ #import "OVUTF8Helper.h" using namespace std; -using namespace Formosa::Gramambular; +using namespace Taiyan::Gramambular; using namespace vChewing; using namespace OpenVanilla; static const int kUserOverrideModelCapacity = 500; static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. -vChewingLM gLanguageModelBopomofo; -vChewingLM gLanguageModelSimpBopomofo; +vChewingLM glanguageModelCoreCHT; +vChewingLM glanguageModelCoreCHS; UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); @implementation LanguageModelManager @@ -36,19 +36,19 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (void)loadDataModels { - LTLoadLanguageModelFile(@"data-cht", gLanguageModelBopomofo); - LTLoadLanguageModelFile(@"data-chs", gLanguageModelSimpBopomofo); + LTLoadLanguageModelFile(@"data-cht", glanguageModelCoreCHT); + LTLoadLanguageModelFile(@"data-chs", glanguageModelCoreCHS); } + (void)loadUserPhrases { - gLanguageModelBopomofo.loadUserPhrases([[self userPhrasesDataPathBopomofo] UTF8String], [[self excludedPhrasesDataPathBopomofo] UTF8String]); - gLanguageModelSimpBopomofo.loadUserPhrases(NULL, [[self excludedPhrasesDataPathSimpBopomofo] UTF8String]); + glanguageModelCoreCHT.loadUserPhrases([[self userPhrasesDataPathBopomofo] UTF8String], [[self excludedPhrasesDataPathBopomofo] UTF8String]); + glanguageModelCoreCHS.loadUserPhrases(NULL, [[self excludedPhrasesDataPathSimpBopomofo] UTF8String]); } + (void)loadUserPhraseReplacement { - gLanguageModelBopomofo.loadPhraseReplacementMap([[self phraseReplacementDataPathBopomofo] UTF8String]); + glanguageModelCoreCHT.loadPhraseReplacementMap([[self phraseReplacementDataPathBopomofo] UTF8String]); } + (BOOL)checkIfUserDataFolderExists @@ -183,14 +183,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return [[self dataFolderPath] stringByAppendingPathComponent:@"phrases-replacement.txt"]; } - + (vChewingLM *)languageModelBopomofo + + (vChewingLM *)languageModelCoreCHT { - return &gLanguageModelBopomofo; + return &glanguageModelCoreCHT; } -+ (vChewingLM *)languageModelSimpBopomofo ++ (vChewingLM *)languageModelCoreCHS { - return &gLanguageModelSimpBopomofo; + return &glanguageModelCoreCHS; } + (vChewing::UserOverrideModel *)userOverrideModel diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 718ea55a5..406df11b6 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -20,6 +20,7 @@ private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" private let kChineseConversionEnabledKey = "ChineseConversionEnabled" private let kHalfWidthPunctuationEnabledKey = "HalfWidthPunctuationEnable" private let kEscToCleanInputBufferKey = "EscToCleanInputBuffer" +private let kUseWinNT351BPMF = "UseWinNT351BPMF" private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" private let kCandidateTextFontName = "CandidateTextFontName" @@ -193,6 +194,9 @@ struct ComposingBufferSize { @UserDefault(key: kChooseCandidateUsingSpaceKey, defaultValue: true) @objc static var chooseCandidateUsingSpace: Bool + @UserDefault(key: kUseWinNT351BPMF, defaultValue: false) + @objc static var useWinNT351BPMF: Bool + @UserDefault(key: kShouldNotFartInLieuOfBeep, defaultValue: true) @objc static var shouldNotFartInLieuOfBeep: Bool diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index 02dff6500..b8a760585 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -128,7 +128,7 @@ extension RangeReplaceableCollection where Element: Hashable { @IBAction func clickedWhetherIMEShouldNotFartToggleAction(_ sender: Any) { clsSFX.beep() } - + @IBAction func changeSelectionKeyAction(_ sender: Any) { guard let keys = (sender as AnyObject).stringValue?.trimmingCharacters(in: .whitespacesAndNewlines).charDeDuplicate else { return diff --git a/Source/en.lproj/preferences.strings b/Source/en.lproj/preferences.strings index 8fa9fb3b4..47550c91a 100644 --- a/Source/en.lproj/preferences.strings +++ b/Source/en.lproj/preferences.strings @@ -98,11 +98,14 @@ /* Class = "NSTextFieldCell"; title = "Choose which keys you prefer for selecting candidates."; ObjectID = "2pS-nv-te4"; */ "2pS-nv-te4.title" = "Choose which keys you prefer for selecting candidates."; +/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "62u-jY-BRh"; */ +"62u-jY-BRh.title" = "Stop farting (when typed phonetic combination is invalid, etc.)"; + /* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ "9DS-Rc-TXq.title" = "UI language setting:"; -/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "ArK-Vk-OoT"; */ -"ArK-Vk-OoT.title" = "Stop farting (when typed phonetic combination is invalid, etc.)"; +/* Class = "NSButtonCell"; title = "Emulating Windows NT 3.51 legacy phonetic typing experience"; ObjectID = "ArK-Vk-OoT"; */ +"ArK-Vk-OoT.title" = "Emulating Windows NT 3.51 legacy phonetic typing experience"; /* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ "BSK-bH-Gct.title" = "Auto-convert traditional Chinese glyphs to KangXi characters"; diff --git a/Source/zh-Hans.lproj/preferences.strings b/Source/zh-Hans.lproj/preferences.strings index 2f650fce3..fc997ad8e 100644 --- a/Source/zh-Hans.lproj/preferences.strings +++ b/Source/zh-Hans.lproj/preferences.strings @@ -98,11 +98,14 @@ /* Class = "NSTextFieldCell"; title = "Choose which keys you prefer for selecting candidates."; ObjectID = "2pS-nv-te4"; */ "2pS-nv-te4.title" = "选择您所偏好的用来选字的按键组合。"; +/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "62u-jY-BRh"; */ +"62u-jY-BRh.title" = "不要放屁 // 例:当输入的音韵有误时,等"; + /* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ "9DS-Rc-TXq.title" = "介面语言设定:"; -/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "ArK-Vk-OoT"; */ -"ArK-Vk-OoT.title" = "不要放屁 // 例:当输入的音韵有误时,等。"; +/* Class = "NSButtonCell"; title = "Emulating Windows NT 3.51 legacy phonetic typing experience"; ObjectID = "ArK-Vk-OoT"; */ +"ArK-Vk-OoT.title" = "模拟 Windows NT 3.51 注音逐字选字输入风格"; /* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ "BSK-bH-Gct.title" = "自动将繁体中文字转换为康熙字"; diff --git a/Source/zh-Hant.lproj/preferences.strings b/Source/zh-Hant.lproj/preferences.strings index 1b40d2afd..a4e2bc65e 100644 --- a/Source/zh-Hant.lproj/preferences.strings +++ b/Source/zh-Hant.lproj/preferences.strings @@ -98,11 +98,14 @@ /* Class = "NSTextFieldCell"; title = "Choose which keys you prefer for selecting candidates."; ObjectID = "2pS-nv-te4"; */ "2pS-nv-te4.title" = "選擇您所偏好的用來選字的按鍵組合。"; +/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "62u-jY-BRh"; */ +"62u-jY-BRh.title" = "不要放屁 // 例:當輸入的音韻有誤時,等"; + /* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ "9DS-Rc-TXq.title" = "介面語言設定:"; -/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "ArK-Vk-OoT"; */ -"ArK-Vk-OoT.title" = "不要放屁 // 例:當輸入的音韻有誤時,等。"; +/* Class = "NSButtonCell"; title = "Emulating Windows NT 3.51 legacy phonetic typing experience"; ObjectID = "ArK-Vk-OoT"; */ +"ArK-Vk-OoT.title" = "模擬 Windows NT 3.51 注音逐字選字輸入風格"; /* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ "BSK-bH-Gct.title" = "自動將繁體中文字轉換為康熙字"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 9765791ae..76db6dbd2 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -87,10 +87,10 @@ /* Begin PBXFileReference section */ 5B000FC1278495AD004F02AC /* SimpBopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = SimpBopomofo.tiff; sourceTree = ""; }; 5B000FC2278495AD004F02AC /* SimpBopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "SimpBopomofo@2x.tiff"; sourceTree = ""; }; - 5B054058278787710083EF4A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/preferences.strings; sourceTree = ""; }; 5B19584E27888F5D00FAEB14 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = ""; }; 5B19584F27888F5F00FAEB14 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = ""; }; 5B19585127888F6B00FAEB14 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = ""; }; + 5B4213762796CD0A0089FCF5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/preferences.strings; sourceTree = ""; }; 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = ""; }; 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; 5B42B64127877D6500BB9B9F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/preferences.strings"; sourceTree = ""; }; @@ -207,22 +207,6 @@ 6A225A212367A1D700F685C6 /* ArchiveUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArchiveUtil.h; sourceTree = ""; }; 6A225A222367A1D700F685C6 /* ArchiveUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArchiveUtil.m; sourceTree = ""; }; 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - 6A38BBE215FC117A00A8A51F /* BIG5toUTF8.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = BIG5toUTF8.pl; sourceTree = ""; }; - 6A38BBE315FC117A00A8A51F /* build4wlist.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = build4wlist.bash; sourceTree = ""; }; - 6A38BBE415FC117A00A8A51F /* buildFreq.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = buildFreq.bash; sourceTree = ""; }; - 6A38BBE515FC117A00A8A51F /* cook.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = cook.py; sourceTree = ""; }; - 6A38BBE615FC117A00A8A51F /* cook.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = cook.rb; sourceTree = ""; }; - 6A38BBE715FC117A00A8A51F /* count.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = count.bash; sourceTree = ""; }; - 6A38BBE815FC117A00A8A51F /* count.occurrence.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = count.occurrence.c; sourceTree = ""; }; - 6A38BBE915FC117A00A8A51F /* count.occurrence.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = count.occurrence.pl; sourceTree = ""; }; - 6A38BBEA15FC117A00A8A51F /* count.occurrence.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = count.occurrence.py; sourceTree = ""; }; - 6A38BBEB15FC117A00A8A51F /* countphrase.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = countphrase.bash; sourceTree = ""; }; - 6A38BBEC15FC117A00A8A51F /* filter.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = filter.bash; sourceTree = ""; }; - 6A38BBED15FC117A00A8A51F /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; - 6A38BBEE15FC117A00A8A51F /* randomShuffle.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = randomShuffle.bash; sourceTree = ""; }; - 6A38BBEF15FC117A00A8A51F /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = ""; }; - 6A38BBF015FC117A00A8A51F /* typocorrection.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = typocorrection.bash; sourceTree = ""; }; - 6A38BBF115FC117A00A8A51F /* utf8length.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = utf8length.pl; sourceTree = ""; }; 6A38BBF615FC117A00A8A51F /* data-cht.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-cht.txt"; sourceTree = ""; }; 6A38BBFA15FC117A00A8A51F /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 6A38BC2715FC158A00A8A51F /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/InputMethodKit.framework; sourceTree = DEVELOPER_DIR; }; @@ -513,7 +497,6 @@ 6A38BBDD15FC115800A8A51F /* Data */ = { isa = PBXGroup; children = ( - 6A38BBE115FC117A00A8A51F /* bin */, 6A38BBF615FC117A00A8A51F /* data-cht.txt */, 5BC3FB82278492DE0022E99A /* data-chs.txt */, 6A38BBFA15FC117A00A8A51F /* Makefile */, @@ -521,29 +504,6 @@ path = Data; sourceTree = ""; }; - 6A38BBE115FC117A00A8A51F /* bin */ = { - isa = PBXGroup; - children = ( - 6A38BBE215FC117A00A8A51F /* BIG5toUTF8.pl */, - 6A38BBE315FC117A00A8A51F /* build4wlist.bash */, - 6A38BBE415FC117A00A8A51F /* buildFreq.bash */, - 6A38BBE515FC117A00A8A51F /* cook.py */, - 6A38BBE615FC117A00A8A51F /* cook.rb */, - 6A38BBE715FC117A00A8A51F /* count.bash */, - 6A38BBE815FC117A00A8A51F /* count.occurrence.c */, - 6A38BBE915FC117A00A8A51F /* count.occurrence.pl */, - 6A38BBEA15FC117A00A8A51F /* count.occurrence.py */, - 6A38BBEB15FC117A00A8A51F /* countphrase.bash */, - 6A38BBEC15FC117A00A8A51F /* filter.bash */, - 6A38BBED15FC117A00A8A51F /* Makefile */, - 6A38BBEE15FC117A00A8A51F /* randomShuffle.bash */, - 6A38BBEF15FC117A00A8A51F /* README */, - 6A38BBF015FC117A00A8A51F /* typocorrection.bash */, - 6A38BBF115FC117A00A8A51F /* utf8length.pl */, - ); - path = bin; - sourceTree = ""; - }; 6ACA41E715FC1D9000935EF6 /* Installer */ = { isa = PBXGroup; children = ( @@ -834,7 +794,7 @@ 6A15B32721A51F2300B92CD3 /* Base */, 5B42B64127877D6500BB9B9F /* zh-Hans */, 5B42B64227877D7700BB9B9F /* zh-Hant */, - 5B054058278787710083EF4A /* en */, + 5B4213762796CD0A0089FCF5 /* en */, ); name = preferences.xib; path = ..; -- Gitee From e8475c8723e3ef9b34d6a5615ab72fe45eacdec7 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 18 Jan 2022 19:53:50 +0800 Subject: [PATCH 068/163] =?UTF-8?q?=E7=B0=A1=E9=AB=94=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E7=A6=81=E7=94=A8=E5=BA=B7=E7=86=99=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E7=86=B1=E9=8D=B5=E8=88=87=E9=81=B8=E5=96=AE=E9=A0=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Disabling KangXi mode menu & hotkeys in CHS mode. --- Source/InputMethodController.mm | 14 +++++++++----- Source/en.lproj/Localizable.strings | 2 +- Source/zh-Hans.lproj/Localizable.strings | 2 +- Source/zh-Hant.lproj/Localizable.strings | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 44335a869..89f642719 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -138,9 +138,11 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItemWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; - NSMenuItem *chineseConversionMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; - chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; - chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; + if ((_inputMode == kBopomofoModeIdentifierCHT) && optionKeyPressed) { + NSMenuItem *chineseConversionMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Force KangXi Writing", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; + chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; + chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; + } NSMenuItem *halfWidthPunctuationMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""]; halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; @@ -1477,8 +1479,10 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)toggleChineseConverter:(id)sender { - BOOL chineseConversionEnabled = [Preferences toggleChineseConversionEnabled]; - [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Chinese Conversion", @""), @"\n", chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; + if (_inputMode == kBopomofoModeIdentifierCHT) { + BOOL chineseConversionEnabled = [Preferences toggleChineseConversionEnabled]; + [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Force KangXi Writing", @""), @"\n", chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; + } } - (void)toggleHalfWidthPunctuation:(id)sender diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 31e4e6b39..98c255ef6 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -16,7 +16,7 @@ "Not Now" = "Not Now"; "Visit Website" = "Visit Website"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@"; -"Chinese Conversion" = "Convert zh-TW Kanji to KangXi Variants"; +"Force KangXi Writing" = "Force KangXi Writing"; "NotificationSwitchON" = "✔ ON"; "NotificationSwitchOFF" = "✘ OFF"; "Edit User Phrases" = "Edit User Phrases"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index ed3192835..a21f485ee 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -16,7 +16,7 @@ "Not Now" = "以后再说"; "Visit Website" = "前往网站"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),网路上有更新版本 %3$@ (%4$@) 可供下载。是否要前往威注音网站下载新版来安装?%5$@"; -"Chinese Conversion" = "优先使用康熙繁体字"; +"Force KangXi Writing" = "康熙繁体字模式"; "NotificationSwitchON" = "✔ 已启用"; "NotificationSwitchOFF" = "✘ 已停用"; "Edit User Phrases" = "编辑自订语汇"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 1cdb5d721..906b742b5 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -16,7 +16,7 @@ "Not Now" = "以後再說"; "Visit Website" = "前往網站"; "You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "目前使用的威注音官方版本是 %1$@ (%2$@),網路上有更新版本 %3$@ (%4$@) 可供下載。是否要前往威注音網站下載新版來安裝?%5$@"; -"Chinese Conversion" = "優先使用康熙繁體字"; +"Force KangXi Writing" = "康熙繁體字模式"; "NotificationSwitchON" = "✔ 已啟用"; "NotificationSwitchOFF" = "✘ 已停用"; "Edit User Phrases" = "編輯自訂語彙"; -- Gitee From b0c9d9298969848f7e73c4b3a0862533c38b0684 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 24 Jan 2022 11:07:43 +0800 Subject: [PATCH 069/163] Shiki: i18n // Japanese Localization. --- LICENSE-JPN.txt | 14 + Source/Base.lproj/preferences.xib | 1 + Source/Installer/en.lproj/Localizable.strings | 3 - Source/Installer/ja.lproj/InfoPlist.strings | 5 + Source/Installer/ja.lproj/Localizable.strings | 18 ++ Source/Installer/ja.lproj/MainMenu.strings | 72 +++++ .../zh-Hans.lproj/Localizable.strings | 1 - .../zh-Hant.lproj/Localizable.strings | 1 - Source/en.lproj/Localizable.strings | 5 +- Source/en.lproj/preferences.strings | 3 + Source/ja.lproj/BSDLicense.txt | 9 + Source/ja.lproj/InfoPlist.strings | 6 + Source/ja.lproj/Localizable.strings | 34 ++ Source/ja.lproj/frmAboutWindow.strings | 27 ++ Source/ja.lproj/preferences.strings | 183 +++++++++++ Source/zh-Hans.lproj/Localizable.strings | 5 +- Source/zh-Hans.lproj/MainMenu.xib | 296 ------------------ Source/zh-Hans.lproj/preferences.strings | 3 + Source/zh-Hant.lproj/Localizable.strings | 5 +- Source/zh-Hant.lproj/MainMenu.xib | 295 ----------------- Source/zh-Hant.lproj/frmAboutWindow.strings | 2 +- Source/zh-Hant.lproj/preferences.strings | 3 + vChewing.xcodeproj/project.pbxproj | 25 +- 23 files changed, 401 insertions(+), 615 deletions(-) create mode 100644 LICENSE-JPN.txt create mode 100644 Source/Installer/ja.lproj/InfoPlist.strings create mode 100644 Source/Installer/ja.lproj/Localizable.strings create mode 100644 Source/Installer/ja.lproj/MainMenu.strings create mode 100644 Source/ja.lproj/BSDLicense.txt create mode 100644 Source/ja.lproj/InfoPlist.strings create mode 100644 Source/ja.lproj/Localizable.strings create mode 100644 Source/ja.lproj/frmAboutWindow.strings create mode 100644 Source/ja.lproj/preferences.strings delete mode 100644 Source/zh-Hans.lproj/MainMenu.xib delete mode 100644 Source/zh-Hant.lproj/MainMenu.xib diff --git a/LICENSE-JPN.txt b/LICENSE-JPN.txt new file mode 100644 index 000000000..db9eadc15 --- /dev/null +++ b/LICENSE-JPN.txt @@ -0,0 +1,14 @@ +vChewing macOS: 3-Clause BSD License 3条項BSDライセンス + +© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project. +小麦注音入力エンジン開発:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, など。 +威注音 macOS 版の開発協力:Hiraku Wang。 +威注音語彙データの維持:孫志貴 (Shiki Suen)。 + +ソースコード形式かバイナリ形式か、変更するかしないかを問わず、以下の条件を満たす場合に限り、再頒布および使用が許可されます。 + +① ソースコードを再頒布する場合、上記の著作権表示、本条件一覧、および下記免責条項を含めること。 +② バイナリ形式で再頒布する場合、頒布物に付属のドキュメント等の資料に、上記の著作権表示、本条件一覧、および下記免責条項を含めること。 +③ 書面による特別の許可なしに、本ソフトウェアから派生した製品の宣伝または販売促進に、「威注音」の名前または諸貢献者の名前を使用してはならない。 + +本ソフトウェアは、著作権者および諸貢献者によって「現状のまま」提供されており、明示黙示を問わず、商業的な使用可能性、および特定の目的に対する適合性に関する暗黙の保証も含め、またそれに限定されない、いかなる保証もありません。著作権者も諸貢献者も、事由のいかんを問わず、 損害発生の原因いかんを問わず、かつ責任の根拠が契約であるか厳格責任であるか(過失その他の)不法行為であるかを問わず、仮にそのような損害が発生する可能性を知らされていたとしても、本ソフトウェアの使用によって発生した(代替品または代用サービスの調達、使用の喪失、データの喪失、利益の喪失、業務の中断も含め、またそれに限定されない)直接損害、間接損害、偶発的な損害、特別損害、懲罰的損害、または結果損害について、一切責任を負わないものとします。 diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index f361f0719..2075d5a53 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -102,6 +102,7 @@ + diff --git a/Source/Installer/en.lproj/Localizable.strings b/Source/Installer/en.lproj/Localizable.strings index e10bff264..fc23967c0 100644 --- a/Source/Installer/en.lproj/Localizable.strings +++ b/Source/Installer/en.lproj/Localizable.strings @@ -1,4 +1,3 @@ -"%@ (for version %@)" = "%1$@ (for version %2$@)"; "Upgrade" = "Accept & Upgrade"; "Cancel" = "Cancel"; "Cannot activate the input method." = "Cannot activate the input method."; @@ -7,11 +6,9 @@ "Installation Successful" = "Installation Successful"; "OK" = "OK"; "vChewing is ready to use." = "vChewing is ready to use."; - "Stopping the old version. This may take up to one minute…" = "Stopping the old version. This may take up to one minute…"; "Attention" = "Attention"; "vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "vChewing is upgraded, but please log out or reboot for the new version to be fully functional."; - "Fatal Error" = "Fatal Error"; "Abort" = "Abort"; "Cannot register input source %@ at %@." = "Cannot register input source %@ at %@."; diff --git a/Source/Installer/ja.lproj/InfoPlist.strings b/Source/Installer/ja.lproj/InfoPlist.strings new file mode 100644 index 000000000..e517fe1f9 --- /dev/null +++ b/Source/Installer/ja.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +/* Localized versions of Info.plist keys */ + +CFBundleName = "威注音入力 実装用アプリ"; +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; +CFEULAContent = "ソースコード形式かバイナリ形式か、変更するかしないかを問わず、以下の条件を満たす場合に限り、再頒布および使用が許可されます。\n\n① ソースコードを再頒布する場合、上記の著作権表示、本条件一覧、および下記免責条項を含めること。\n② バイナリ形式で再頒布する場合、頒布物に付属のドキュメント等の資料に、上記の著作権表示、本条件一覧、および下記免責条項を含めること。\n③ 書面による特別の許可なしに、本ソフトウェアから派生した製品の宣伝または販売促進に、「威注音」の名前または諸貢献者の名前を使用してはならない。\n\n本ソフトウェアは、著作権者および諸貢献者によって「現状のまま」提供されており、明示黙示を問わず、商業的な使用可能性、および特定の目的に対する適合性に関する暗黙の保証も含め、またそれに限定されない、いかなる保証もありません。著作権者も諸貢献者も、事由のいかんを問わず、 損害発生の原因いかんを問わず、かつ責任の根拠が契約であるか厳格責任であるか(過失その他の)不法行為であるかを問わず、仮にそのような損害が発生する可能性を知らされていたとしても、本ソフトウェアの使用によって発生した(代替品または代用サービスの調達、使用の喪失、データの喪失、利益の喪失、業務の中断も含め、またそれに限定されない)直接損害、間接損害、偶発的な損害、特別損害、懲罰的損害、または結果損害について、一切責任を負わないものとします。\n"; diff --git a/Source/Installer/ja.lproj/Localizable.strings b/Source/Installer/ja.lproj/Localizable.strings new file mode 100644 index 000000000..44d4c467e --- /dev/null +++ b/Source/Installer/ja.lproj/Localizable.strings @@ -0,0 +1,18 @@ +"Upgrade" = "承認と更新"; +"Cancel" = "取消"; +"Cannot activate the input method." = "入力アプリ、起動失敗。"; +"Cannot copy the file to the destination." = "目標へファイルのコピーできません。"; +"Install Failed" = "実装失敗。"; +"Installation Successful" = "実装完了"; +"OK" = "うむ"; +"vChewing is ready to use." = "威注音入力、利用準備完了。"; +"Stopping the old version. This may take up to one minute…" = "古いバージョンを強制停止中。1分かかると恐れ入りますが……"; +"Attention" = "ご注意"; +"vChewing is upgraded, but please log out or reboot for the new version to be fully functional." = "威注音入力の更新は実装完了しましたが、うまく作動できるために、このパソコンの再起動および再ログインが必要だと恐れ入ります。"; +"Fatal Error" = "致命錯乱"; +"Abort" = "中止"; +"Cannot register input source %@ at %@." = "「%2$@」で入力アプリ「\"%1$@\"」の実装は失敗しました。"; +"Cannot find input source %@ after registration." = "登録済みですが「%@」は見つけませんでした。"; +"Warning" = "警告"; +"Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "入力アプリの自動起動はうまく出来なかったかもしれません。ご自分で「システム環境設定→キーボード→入力ソース」で起動してください。"; +"Continue" = "続行"; diff --git a/Source/Installer/ja.lproj/MainMenu.strings b/Source/Installer/ja.lproj/MainMenu.strings new file mode 100644 index 000000000..5c86d3b61 --- /dev/null +++ b/Source/Installer/ja.lproj/MainMenu.strings @@ -0,0 +1,72 @@ + +/* Class = "NSMenu"; title = "AMainMenu"; ObjectID = "29"; */ +"29.title" = "AMainMenu"; + +/* Class = "NSMenuItem"; title = "vChewing Installer"; ObjectID = "56"; */ +"56.title" = "威注音入力 実装用アプリ"; + +/* Class = "NSMenu"; title = "vChewing Installer"; ObjectID = "57"; */ +"57.title" = "威注音入力 実装用アプリ"; + +/* Class = "NSMenuItem"; title = "About vChewing Installer"; ObjectID = "58"; */ +"58.title" = "威注音入力 実装用アプリについて…"; + +/* Class = "NSMenuItem"; title = "File"; ObjectID = "83"; */ +"83.title" = "ファイル"; + +/* Class = "NSMenu"; title = "Services"; ObjectID = "130"; */ +"130.title" = "サービス"; + +/* Class = "NSMenuItem"; title = "Services"; ObjectID = "131"; */ +"131.title" = "サービス"; + +/* Class = "NSMenuItem"; title = "Hide vChewing Installer"; ObjectID = "134"; */ +"134.title" = "全ウィンドウ隠す"; + +/* Class = "NSMenuItem"; title = "Quit vChewing Installer"; ObjectID = "136"; */ +"136.title" = "威注音入力 実装用アプリ を終了"; + +/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */ +"145.title" = "他のアプリのウィンドウを隠す"; + +/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */ +"150.title" = "隠したウィンドウを全部表示する"; + +/* Class = "NSWindow"; title = "vChewing Installer"; ObjectID = "371"; */ +"371.title" = "威注音入力 実装用アプリ"; + +/* Class = "NSButtonCell"; title = "I Accept"; ObjectID = "576"; */ +"576.title" = "承認する"; + +/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "593"; */ +"593.title" = "取消"; + +/* Class = "NSTextFieldCell"; title = "3-Clause BSD License:"; ObjectID = "AVS-ih-FXM"; */ +"AVS-ih-FXM.title" = "3条項BSDライセンス (3-Clause BSD License):"; + +/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "GNc-8S-1VG"; */ +"GNc-8S-1VG.title" = "vChewing for macOS"; + +/* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "JRP-At-H9q"; */ +"JRP-At-H9q.title" = "version_placeholder"; + +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; ObjectID = "Q9M-ni-kUM"; */ +"Q9M-ni-kUM.title" = "免責事項:vChewing Project は、OpenVanilla と協力関係や提携関係にあるわけではなく、OpenVanilla が小麦注音プロジェクトに同梱した辞書データについて、vChewing Project は一切責任負い兼ねる。特定な地政学的・観念形態的な内容は、vChewing アプリの世界的な普及に妨害する恐れがあるため、vChewing 公式辞書データから削除済みである。"; + +/* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "QYf-Nf-hoi"; */ +"QYf-Nf-hoi.title" = "OpenVanilla 小麦注音プロジェクトから派生。"; + +/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "VW8-s5-Wpn"; */ +"VW8-s5-Wpn.title" = "小麦注音入力エンジン開発:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, など。\nmacOS 版の開発協力:Hiraku Wang。\n威注音語彙データの維持:孫志貴 (Shiki Suen)。"; + +/* Class = "NSTextFieldCell"; title = "Placeholder for showing copyright information."; ObjectID = "eo3-TK-0rB"; */ +"eo3-TK-0rB.title" = "Placeholder for showing copyright information."; + +/* Class = "NSWindow"; title = "Window"; ObjectID = "gHl-Hx-eQn"; */ +"gHl-Hx-eQn.title" = "Window"; + +/* Class = "NSTextFieldCell"; title = "By installing the software, you must accept the terms above."; ObjectID = "mf8-6e-z7X"; */ +"mf8-6e-z7X.title" = "このアプリを実装するために、上記の条約を承認すべきである。"; + +/* Class = "NSTextFieldCell"; title = "Stopping the old version. This may take up to one minute…"; ObjectID = "nTo-dx-qfZ"; */ +"nTo-dx-qfZ.title" = "古いバージョンを強制停止中。1分かかると恐れ入りますが……"; diff --git a/Source/Installer/zh-Hans.lproj/Localizable.strings b/Source/Installer/zh-Hans.lproj/Localizable.strings index 44d0cab0c..2eae654c1 100644 --- a/Source/Installer/zh-Hans.lproj/Localizable.strings +++ b/Source/Installer/zh-Hans.lproj/Localizable.strings @@ -1,4 +1,3 @@ -"%@ (for version %@)" = "%1$@ (%2$@ 版)"; "Upgrade" = "接受并升级"; "Cancel" = "取消"; "Cannot activate the input method." = "无法启用输入法。"; diff --git a/Source/Installer/zh-Hant.lproj/Localizable.strings b/Source/Installer/zh-Hant.lproj/Localizable.strings index d98df05d0..3e19edc47 100644 --- a/Source/Installer/zh-Hant.lproj/Localizable.strings +++ b/Source/Installer/zh-Hant.lproj/Localizable.strings @@ -1,4 +1,3 @@ -"%@ (for version %@)" = "%1$@ (%2$@ 版)"; "Upgrade" = "接受並升級"; "Cancel" = "取消"; "Cannot activate the input method." = "無法啟用輸入法。"; diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 98c255ef6..6f67322e1 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "About vChewing…" = "About vChewing…"; -"Clear Learning Dictionary (%ju Items)" = "Clear Learning Dictionary (%ju Items)"; -"Dump Learning Data to Console" = "Dump Learning Data to Console"; -"Enable Selection Learning" = "Enable Selection Learning"; -"vChewing Preferences" = "vChewing Preferences"; +"vChewing Preferences" = "vChewing Preferences…"; "Check Later" = "Check Later"; "Check for Updates…" = "Check for Updates…"; "Check for Update Completed" = "Check for Update Completed"; diff --git a/Source/en.lproj/preferences.strings b/Source/en.lproj/preferences.strings index 47550c91a..720c9c684 100644 --- a/Source/en.lproj/preferences.strings +++ b/Source/en.lproj/preferences.strings @@ -170,6 +170,9 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; +/* Class = "NSMenuItem"; title = "Japanese"; ObjectID = "rVQ-Hx-cGi"; */ +"rVQ-Hx-cGi.title" = "Japanese"; + /* Class = "NSBox"; title = "Dictionary Settings"; ObjectID = "s4r-ji-vbr"; */ "s4r-ji-vbr.title" = "Dictionary Settings"; diff --git a/Source/ja.lproj/BSDLicense.txt b/Source/ja.lproj/BSDLicense.txt new file mode 100644 index 000000000..672bf0b03 --- /dev/null +++ b/Source/ja.lproj/BSDLicense.txt @@ -0,0 +1,9 @@ +vChewing macOS: 3-Clause BSD License 3条項BSDライセンス + +ソースコード形式かバイナリ形式か、変更するかしないかを問わず、以下の条件を満たす場合に限り、再頒布および使用が許可されます。 + +① ソースコードを再頒布する場合、上記の著作権表示、本条件一覧、および下記免責条項を含めること。 +② バイナリ形式で再頒布する場合、頒布物に付属のドキュメント等の資料に、上記の著作権表示、本条件一覧、および下記免責条項を含めること。 +③ 書面による特別の許可なしに、本ソフトウェアから派生した製品の宣伝または販売促進に、「威注音」の名前または諸貢献者の名前を使用してはならない。 + +本ソフトウェアは、著作権者および諸貢献者によって「現状のまま」提供されており、明示黙示を問わず、商業的な使用可能性、および特定の目的に対する適合性に関する暗黙の保証も含め、またそれに限定されない、いかなる保証もありません。著作権者も諸貢献者も、事由のいかんを問わず、 損害発生の原因いかんを問わず、かつ責任の根拠が契約であるか厳格責任であるか(過失その他の)不法行為であるかを問わず、仮にそのような損害が発生する可能性を知らされていたとしても、本ソフトウェアの使用によって発生した(代替品または代用サービスの調達、使用の喪失、データの喪失、利益の喪失、業務の中断も含め、またそれに限定されない)直接損害、間接損害、偶発的な損害、特別損害、懲罰的損害、または結果損害について、一切責任を負わないものとします。 diff --git a/Source/ja.lproj/InfoPlist.strings b/Source/ja.lproj/InfoPlist.strings new file mode 100644 index 000000000..1c5655bd7 --- /dev/null +++ b/Source/ja.lproj/InfoPlist.strings @@ -0,0 +1,6 @@ +CFBundleName = "vChewing"; +CFBundleDisplayName = "vChewing"; +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; +"org.atelierInmu.inputmethod.vChewing.TradBopomofo" = "繁体威注音"; +"org.atelierInmu.inputmethod.vChewing.SimpBopomofo" = "簡体威注音"; +CFEULAContent = "ソースコード形式かバイナリ形式か、変更するかしないかを問わず、以下の条件を満たす場合に限り、再頒布および使用が許可されます。\n\n① ソースコードを再頒布する場合、上記の著作権表示、本条件一覧、および下記免責条項を含めること。\n② バイナリ形式で再頒布する場合、頒布物に付属のドキュメント等の資料に、上記の著作権表示、本条件一覧、および下記免責条項を含めること。\n③ 書面による特別の許可なしに、本ソフトウェアから派生した製品の宣伝または販売促進に、「威注音」の名前または諸貢献者の名前を使用してはならない。\n\n本ソフトウェアは、著作権者および諸貢献者によって「現状のまま」提供されており、明示黙示を問わず、商業的な使用可能性、および特定の目的に対する適合性に関する暗黙の保証も含め、またそれに限定されない、いかなる保証もありません。著作権者も諸貢献者も、事由のいかんを問わず、 損害発生の原因いかんを問わず、かつ責任の根拠が契約であるか厳格責任であるか(過失その他の)不法行為であるかを問わず、仮にそのような損害が発生する可能性を知らされていたとしても、本ソフトウェアの使用によって発生した(代替品または代用サービスの調達、使用の喪失、データの喪失、利益の喪失、業務の中断も含め、またそれに限定されない)直接損害、間接損害、偶発的な損害、特別損害、懲罰的損害、または結果損害について、一切責任を負わないものとします。\n"; diff --git a/Source/ja.lproj/Localizable.strings b/Source/ja.lproj/Localizable.strings new file mode 100644 index 000000000..5afd2ab9a --- /dev/null +++ b/Source/ja.lproj/Localizable.strings @@ -0,0 +1,34 @@ +/* No comment provided by engineer. */ +"About vChewing…" = "威注音について…"; +"vChewing Preferences" = "入力機能設定…"; +"Check Later" = "後でやる"; +"Check for Updates…" = "更新通知を受く…"; +"Check for Update Completed" = "更新通知受信完了"; +"You are already using the latest version of vChewing." = "今の威注音はすでに最新版だと報告します。"; +"Update Check Failed" = "更新通知受信失敗"; +"There may be no internet connection or the server failed to respond.\n\nError message: %@" = "ネットの接続の有無の問題か、サーバー側の反応の問題か。\n\nいずれにせよ、エラーメッセージ:「%@」"; +"OK" = "うむ"; +"Dismiss" = "却下"; +"New Version Available" = "最新版利用可能"; +"Not Now" = "後で"; +"Visit Website" = "公式サイト"; +"You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@" = "今のご使用していた威注音入力アプリのバージョンは「%1$@ (%2$@)」であり、ネットでもっと新しいバージョン「%3$@ (%4$@)」の下載せはできるらしい。公式サイトへバージョン「%5$@」を下載せますか?"; +"Force KangXi Writing" = "康熙文字変換モード"; +"NotificationSwitchON" = "✔ 機能起動"; +"NotificationSwitchOFF" = "✘ 機能停止"; +"Edit User Phrases" = "ユーザー辞書を編集"; +"Reload User Phrases" = "ユーザー辞書を再び読込む"; +"Unable to create the user phrase file." = "ユーザー辞書ファイルの作成は失敗しました。"; +"Please check the permission of at \"%@\"." = "「%@」に書き出す権限は不足らしい。"; +"Edit Excluded Phrases" = "辞書条目排除表を編集"; +"Use Half-Width Punctuations" = "半角句読機能を起用"; +"\"%@\" length must ≥ 2 for a user phrase." = "「%@」もう1つ文字のお選びを。"; +"\"%@\" selected. ENTER to add user phrase." = "「%@」を ENTER で辞書に登録。"; +"Edit Phrase Replacement Table" = "言葉置換表を編集"; +"Use Phrase Replacement" = "言葉置換機能"; +"Candidates keys cannot be empty." = "言選り用キー陣列に何かキーをご登録ください。"; +"Candidate keys can only contain ASCII characters like alphanumerals." = "言選り用キー陣列にはASCII文字だけをご登録ください(英数など)。"; +"Candidate keys cannot contain space." = "言選り用キー陣列にスペースキーは登録できません。"; +"There should not be duplicated keys." = "言選り用キー陣列に同じキーの重複登録はできません。"; +"Please specify at least 4 candidate keys." = "言選り用キー陣列に少なくとも4つのキーをご登録ください。"; +"Maximum 15 candidate keys allowed." = "言選り用キー陣列には最多15つキー登録できます。"; diff --git a/Source/ja.lproj/frmAboutWindow.strings b/Source/ja.lproj/frmAboutWindow.strings new file mode 100644 index 000000000..34913371f --- /dev/null +++ b/Source/ja.lproj/frmAboutWindow.strings @@ -0,0 +1,27 @@ + +/* Class = "NSButtonCell"; title = "OK"; ObjectID = "btnConfirm"; */ +"btnConfirm.title" = "OK"; + +/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "lblAppTitle"; */ +"lblAppTitle.title" = "vChewing for macOS"; + +/* Class = "NSTextFieldCell"; title = "Placeholder for showing copyright information."; ObjectID = "lblCopyright"; */ +"lblCopyright.title" = "Placeholder for showing copyright information."; + +/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.\nvChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "lblCredits"; */ +"lblCredits.title" = "小麦注音入力エンジン開発:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, など。\nmacOS 版の開発協力:Hiraku Wang。\n威注音語彙データの維持:孫志貴 (Shiki Suen)。"; + +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project, having no relationship of cooperation or affiliation with the OpenVanilla project, is not responsible for the phrase database shipped in the original McBopomofo project. Certain geopolitical and ideological contents, which are potentially harmful to the global spread of this software, have been removed from vChewing official phrase database."; ObjectID = "lblDisclaimer"; */ +"lblDisclaimer.title" = "免責事項:vChewing Project は、OpenVanilla と協力関係や提携関係にあるわけではなく、OpenVanilla が小麦注音プロジェクトに同梱した辞書データについて、vChewing Project は一切責任負い兼ねる。特定な地政学的・観念形態的な内容は、vChewing アプリの世界的な普及に妨害する恐れがあるため、vChewing 公式辞書データから削除済みである。"; + +/* Class = "NSTextFieldCell"; title = "3-Clause BSD License:"; ObjectID = "lblLicense"; */ +"lblLicense.title" = "3条項BSDライセンス (3-Clause BSD License):"; + +/* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "lblProjectDescription"; */ +"lblProjectDescription.title" = "OpenVanilla 小麦注音プロジェクトから派生。"; + +/* Class = "NSTextFieldCell"; title = "version_placeholder"; ObjectID = "lblVersionString"; */ +"lblVersionString.title" = "version_placeholder"; + +/* Class = "NSWindow"; title = "About vChewing for macOS"; ObjectID = "ttlAboutWindow"; */ +"ttlAboutWindow.title" = "macOS 版威注音入力アプリについて"; diff --git a/Source/ja.lproj/preferences.strings b/Source/ja.lproj/preferences.strings new file mode 100644 index 000000000..911d45768 --- /dev/null +++ b/Source/ja.lproj/preferences.strings @@ -0,0 +1,183 @@ + +/* Class = "NSWindow"; title = "vChewing Preferences"; ObjectID = "1"; */ +"1.title" = "威注音アプリ機能設定"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "5"; */ +"5.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "Standard"; ObjectID = "6"; */ +"6.title" = "標準配列(Microsoft・大千・王安など)"; + +/* Class = "NSMenuItem"; title = "ETen"; ObjectID = "7"; */ +"7.title" = "倚天伝統配列"; + +/* Class = "NSMenuItem"; title = "Hsu"; ObjectID = "8"; */ +"8.title" = "許氏国音自然配列"; + +/* Class = "NSMenuItem"; title = "ETen26"; ObjectID = "9"; */ +"9.title" = "倚天26キー配列"; + +/* Class = "NSMenuItem"; title = "Hanyu Pinyin"; ObjectID = "10"; */ +"10.title" = "漢語弁音(ローマ字+数字音調)"; + +/* Class = "NSTextFieldCell"; title = "Bopomofo:"; ObjectID = "12"; */ +"12.title" = "注音キーボード配列:"; + +/* Class = "NSTextFieldCell"; title = "Choose the cursor position where you want to list possible candidates."; ObjectID = "14"; */ +"14.title" = "カーソルはどこで入力候補を呼び出すかとご選択ください:"; + +/* Class = "NSButtonCell"; title = "Cursor to the front of the phrase (like Matsushita Hanin IME)"; ObjectID = "16"; */ +"16.title" = "単語の前で // パナソニック漢音の入力スタイル"; + +/* Class = "NSButtonCell"; title = "Cursor to the rear of the phrase (like MS New-Phonetic IME)"; ObjectID = "17"; */ +"17.title" = "単語の後で // Microsoft 新注音の入力スタイル"; + +/* Class = "NSButtonCell"; title = "Radio"; ObjectID = "18"; */ +"18.title" = "Radio"; + +/* Class = "NSButtonCell"; title = "Radio"; ObjectID = "20"; */ +"20.title" = "Radio"; + +/* Class = "NSButtonCell"; title = "Horizontal"; ObjectID = "21"; */ +"21.title" = "横型陳列"; + +/* Class = "NSButtonCell"; title = "Vertical"; ObjectID = "22"; */ +"22.title" = "縦型陳列"; + +/* Class = "NSTextFieldCell"; title = "Candidate List Layout:"; ObjectID = "24"; */ +"24.title" = "入力候補陳列の仕様:"; + +/* Class = "NSTextFieldCell"; title = "Candidate UI font size:"; ObjectID = "29"; */ +"29.title" = "候補文字の字号:"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "92"; */ +"92.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "12"; ObjectID = "93"; */ +"93.title" = "12"; + +/* Class = "NSMenuItem"; title = "14"; ObjectID = "94"; */ +"94.title" = "14"; + +/* Class = "NSMenuItem"; title = "16"; ObjectID = "95"; */ +"95.title" = "16"; + +/* Class = "NSMenuItem"; title = "18"; ObjectID = "96"; */ +"96.title" = "18"; + +/* Class = "NSMenuItem"; title = "24"; ObjectID = "98"; */ +"98.title" = "24"; + +/* Class = "NSMenuItem"; title = "32"; ObjectID = "99"; */ +"99.title" = "32"; + +/* Class = "NSMenuItem"; title = "64"; ObjectID = "100"; */ +"100.title" = "64"; + +/* Class = "NSMenuItem"; title = "96"; ObjectID = "101"; */ +"101.title" = "96"; + +/* Class = "NSButtonCell"; title = "Press Space key chooses candidate"; ObjectID = "110"; */ +"110.title" = "スペースキーで入力候補を呼び出す"; + +/* Class = "NSTextFieldCell"; title = "Alphanumeric:"; ObjectID = "126"; */ +"126.title" = "英数キーボード配列:"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "128"; */ +"128.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "IBM"; ObjectID = "137"; */ +"137.title" = "IBM"; + +/* Class = "NSTabViewItem"; label = "Keyboard"; ObjectID = "1AW-xf-c2f"; */ +"1AW-xf-c2f.label" = "キーボード"; + +/* Class = "NSBox"; title = "General Settings"; ObjectID = "2Y6-Am-WM1"; */ +"2Y6-Am-WM1.title" = "全般設定"; + +/* Class = "NSTextFieldCell"; title = "Choose which keys you prefer for selecting candidates."; ObjectID = "2pS-nv-te4"; */ +"2pS-nv-te4.title" = "お好きなる言選り用キー陣列をお選びください。"; + +/* Class = "NSButtonCell"; title = "Stop farting (when typed phonetic combination is invalid, etc.)"; ObjectID = "62u-jY-BRh"; */ +"62u-jY-BRh.title" = "マナーモード // 外すと入力間違の時に変な音が出る"; + +/* Class = "NSTextFieldCell"; title = "UI language setting:"; ObjectID = "9DS-Rc-TXq"; */ +"9DS-Rc-TXq.title" = "アプリ表示用言語:"; + +/* Class = "NSButtonCell"; title = "Emulating Windows NT 3.51 legacy phonetic typing experience"; ObjectID = "ArK-Vk-OoT"; */ +"ArK-Vk-OoT.title" = "Windows NT 3.51 内蔵注音入力スタイルを真似する"; + +/* Class = "NSButtonCell"; title = "Auto-convert traditional Chinese glyphs to KangXi characters"; ObjectID = "BSK-bH-Gct"; */ +"BSK-bH-Gct.title" = "自動的に繁体漢字を康熙文字と変換する"; + +/* Class = "NSBox"; title = "Advanced Settings"; ObjectID = "E1l-m8-xgb"; */ +"E1l-m8-xgb.title" = "詳細設定"; + +/* Class = "NSMenuItem"; title = "English"; ObjectID = "FSG-lN-CJO"; */ +"FSG-lN-CJO.title" = "英語"; + +/* Class = "NSTextFieldCell"; title = "Selection Keys:"; ObjectID = "FnD-oH-El5"; */ +"FnD-oH-El5.title" = "言選り用キー:"; + +/* Class = "NSMenuItem"; title = "Auto-Select"; ObjectID = "GlJ-Ns-9eE"; */ +"GlJ-Ns-9eE.title" = "システム設定に準ずる"; + +/* Class = "NSTabViewItem"; label = "Dictionary"; ObjectID = "ISh-Da-hKv"; */ +"ISh-Da-hKv.label" = "辞書"; + +/* Class = "NSTabViewItem"; label = "General"; ObjectID = "QUQ-oY-4Hc"; */ +"QUQ-oY-4Hc.label" = "全般"; + +/* Class = "NSTextFieldCell"; title = "Choose your preferred keyboard layout."; ObjectID = "RQ6-MS-m4C"; */ +"RQ6-MS-m4C.title" = "お好きなるキーボード配列をお選びください。"; + +/* Class = "NSMenuItem"; title = "Traditional Chinese"; ObjectID = "TXr-FF-ehw"; */ +"TXr-FF-ehw.title" = "繁体中国語"; + +/* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ +"Uyz-xL-TVN.title" = "出力設定"; + +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "全字庫モード // ほぼ全ての Unicode 漢字を入力できる"; + +/* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ +"Wvt-HE-LOv.title" = "キーボード配列"; + +/* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ +"Z9t-P0-BLF.title" = "アプリの更新通知を受く"; + +/* Class = "NSTextFieldCell"; title = "Change user interface language."; ObjectID = "ZEv-Q2-mYL"; */ +"ZEv-Q2-mYL.title" = "アプリ表示用言語をご指定ください。"; + +/* Class = "NSMenuItem"; title = "Simplified Chinese"; ObjectID = "akC-2g-ybz"; */ +"akC-2g-ybz.title" = "簡体中国語"; + +/* Class = "NSButtonCell"; title = "Press ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ +"f2j-xD-4xK.title" = "ESC キーで入力緩衝列を消す"; + +/* Class = "NSTextFieldCell"; title = "Change UI font size of candidate window for a better visual clarity."; ObjectID = "iRg-wx-Nx2"; */ +"iRg-wx-Nx2.title" = "入力候補陣列の候補文字の字号をご指定ください。"; + +/* Class = "NSTextFieldCell"; title = "This dictionary page is under development."; ObjectID = "j48-5a-cEs"; */ +"j48-5a-cEs.title" = "このページの機能はまだまだ施工中ですが……"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[1] = "Item 2"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[1]" = "Item 2"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; + +/* Class = "NSMenuItem"; title = "Japanese"; ObjectID = "rVQ-Hx-cGi"; */ +"rVQ-Hx-cGi.title" = "和語"; + +/* Class = "NSBox"; title = "Dictionary Settings"; ObjectID = "s4r-ji-vbr"; */ +"s4r-ji-vbr.title" = "辞書設定"; + +/* Class = "NSTextFieldCell"; title = "Choose your preferred layout of the candidate window."; ObjectID = "xC5-yV-1W1"; */ +"xC5-yV-1W1.title" = "入力候補陳列の仕様をご指定ください。"; + +/* Class = "NSTabViewItem"; label = "Advanced"; ObjectID = "xrE-8T-WKO"; */ +"xrE-8T-WKO.label" = "詳細"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index a21f485ee..f88e98674 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "About vChewing…" = "关于威注音…"; -"Clear Learning Dictionary (%ju Items)" = "清除学习辞典 (%ju 个项目)"; -"Dump Learning Data to Console" = "将学习辞典内容输出到 Console 上"; -"Enable Selection Learning" = "使用自动学习功能"; -"vChewing Preferences" = "威注音偏好设定"; +"vChewing Preferences" = "威注音偏好设定…"; "Check Later" = "晚点再通知我"; "Check for Updates…" = "检查是否有新版…"; "Check for Update Completed" = "新版检查完毕"; diff --git a/Source/zh-Hans.lproj/MainMenu.xib b/Source/zh-Hans.lproj/MainMenu.xib deleted file mode 100644 index ce657a67b..000000000 --- a/Source/zh-Hans.lproj/MainMenu.xib +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/zh-Hans.lproj/preferences.strings b/Source/zh-Hans.lproj/preferences.strings index fc997ad8e..6d98efec5 100644 --- a/Source/zh-Hans.lproj/preferences.strings +++ b/Source/zh-Hans.lproj/preferences.strings @@ -170,6 +170,9 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; +/* Class = "NSMenuItem"; title = "Japanese"; ObjectID = "rVQ-Hx-cGi"; */ +"rVQ-Hx-cGi.title" = "和语"; + /* Class = "NSBox"; title = "Dictionary Settings"; ObjectID = "s4r-ji-vbr"; */ "s4r-ji-vbr.title" = "辞典设定"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 906b742b5..3ef845c79 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -1,9 +1,6 @@ /* No comment provided by engineer. */ "About vChewing…" = "關於威注音…"; -"Clear Learning Dictionary (%ju Items)" = "清除學習辭典 (%ju 個項目)"; -"Dump Learning Data to Console" = "將學習辭典內容輸出到 Console 上"; -"Enable Selection Learning" = "使用自動學習功能"; -"vChewing Preferences" = "威注音偏好設定"; +"vChewing Preferences" = "威注音偏好設定…"; "Check Later" = "晚點再通知我"; "Check for Updates…" = "檢查是否有新版…"; "Check for Update Completed" = "新版檢查完畢"; diff --git a/Source/zh-Hant.lproj/MainMenu.xib b/Source/zh-Hant.lproj/MainMenu.xib deleted file mode 100644 index 4c79f2b95..000000000 --- a/Source/zh-Hant.lproj/MainMenu.xib +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/zh-Hant.lproj/frmAboutWindow.strings b/Source/zh-Hant.lproj/frmAboutWindow.strings index 199517fd2..df4841a17 100644 --- a/Source/zh-Hant.lproj/frmAboutWindow.strings +++ b/Source/zh-Hant.lproj/frmAboutWindow.strings @@ -23,5 +23,5 @@ /* Class = "NSTextFieldCell"; title = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; ObjectID = "lblCopyright"; */ // "lblCopyright.title" = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; -/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "lblCredits"; */ +/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.vChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "lblCredits"; */ "lblCredits.title" = "小麥注音引擎研發:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, 等。\n威注音 macOS 程式研發協力:Hiraku Wang。\n威注音詞庫維護:孫志貴 (Shiki Suen)。"; diff --git a/Source/zh-Hant.lproj/preferences.strings b/Source/zh-Hant.lproj/preferences.strings index a4e2bc65e..2b16943d4 100644 --- a/Source/zh-Hant.lproj/preferences.strings +++ b/Source/zh-Hant.lproj/preferences.strings @@ -170,6 +170,9 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; +/* Class = "NSMenuItem"; title = "Japanese"; ObjectID = "rVQ-Hx-cGi"; */ +"rVQ-Hx-cGi.title" = "和語"; + /* Class = "NSBox"; title = "Dictionary Settings"; ObjectID = "s4r-ji-vbr"; */ "s4r-ji-vbr.title" = "辭典設定"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 76db6dbd2..c84a7bce6 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -90,7 +90,6 @@ 5B19584E27888F5D00FAEB14 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = ""; }; 5B19584F27888F5F00FAEB14 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = ""; }; 5B19585127888F6B00FAEB14 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = ""; }; - 5B4213762796CD0A0089FCF5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/preferences.strings; sourceTree = ""; }; 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = ""; }; 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; 5B42B64127877D6500BB9B9F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/preferences.strings"; sourceTree = ""; }; @@ -110,7 +109,6 @@ 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; - 5B9781D92763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.xib"; sourceTree = ""; }; 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewingInstaller-Bridging-Header.h"; sourceTree = ""; }; 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyValueBlobReader.h; sourceTree = ""; }; 5BC2D2852793B434002C0BEC /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; @@ -121,6 +119,15 @@ 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; }; 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalCandidateController.swift; sourceTree = ""; }; 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; + 5BC4BC542796E6A80023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/MainMenu.strings; sourceTree = ""; }; + 5BC4BC552796E6A80023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/frmAboutWindow.strings; sourceTree = ""; }; + 5BC4BC572796E6A80023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = Source/ja.lproj/preferences.strings; sourceTree = ""; }; + 5BC4BC582796E6A80023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text; name = ja; path = ja.lproj/BSDLicense.txt; sourceTree = ""; }; + 5BC4BC592796E6A90023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = ""; }; + 5BC4BC5A2796E6A90023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 5BC4BC5B2796E6A90023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = Source/ja.lproj/InfoPlist.strings; sourceTree = ""; }; + 5BC4BC5C2796E6A90023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = Source/ja.lproj/Localizable.strings; sourceTree = ""; }; + 5BC4BC5E2796F5C40023BBD5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/preferences.strings; sourceTree = ""; }; 5BD0D19327940E9D0008F48E /* Fart.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.aif; sourceTree = ""; }; 5BD0D19927943D390008F48E /* clsSFX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = clsSFX.swift; sourceTree = ""; }; 5BD0D19E279454F60008F48E /* Beep.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.aif; sourceTree = ""; }; @@ -202,7 +209,6 @@ 6A15B32421A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 6A15B32721A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Source/Base.lproj/preferences.xib; sourceTree = ""; }; - 6A187E2916004C7300466B2E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.xib"; sourceTree = ""; }; 6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = ""; }; 6A225A212367A1D700F685C6 /* ArchiveUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArchiveUtil.h; sourceTree = ""; }; 6A225A222367A1D700F685C6 /* ArchiveUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArchiveUtil.m; sourceTree = ""; }; @@ -607,6 +613,7 @@ "zh-Hant", Base, "zh-Hans", + ja, ); mainGroup = 6A0D4E9215FC0CFA00ABF4B3; packageReferences = ( @@ -751,6 +758,7 @@ 5B58E87E278413E7003EA2AD /* en */, 5B58E880278413EF003EA2AD /* zh-Hans */, 5B58E881278413F1003EA2AD /* zh-Hant */, + 5BC4BC582796E6A80023BBD5 /* ja */, ); name = BSDLicense.txt; sourceTree = ""; @@ -762,6 +770,7 @@ 5BF4A70527844DD2007DC6E7 /* en */, 5BF4A70727844DD3007DC6E7 /* zh-Hant */, 5BF4A70A278451A6007DC6E7 /* zh-Hans */, + 5BC4BC552796E6A80023BBD5 /* ja */, ); name = frmAboutWindow.xib; sourceTree = ""; @@ -772,6 +781,7 @@ 6A0D4F4915FC0EE100ABF4B3 /* en */, 6A0D4F5415FC0EF900ABF4B3 /* zh-Hant */, 5B9781D72763850700897999 /* zh-Hans */, + 5BC4BC5B2796E6A90023BBD5 /* ja */, ); name = InfoPlist.strings; path = ..; @@ -783,6 +793,7 @@ 6A0D4F4B15FC0EE100ABF4B3 /* en */, 6A0D4F5515FC0EF900ABF4B3 /* zh-Hant */, 5B9781D82763850700897999 /* zh-Hans */, + 5BC4BC5C2796E6A90023BBD5 /* ja */, ); name = Localizable.strings; path = ..; @@ -794,7 +805,8 @@ 6A15B32721A51F2300B92CD3 /* Base */, 5B42B64127877D6500BB9B9F /* zh-Hans */, 5B42B64227877D7700BB9B9F /* zh-Hant */, - 5B4213762796CD0A0089FCF5 /* en */, + 5BC4BC572796E6A80023BBD5 /* ja */, + 5BC4BC5E2796F5C40023BBD5 /* en */, ); name = preferences.xib; path = ..; @@ -803,9 +815,7 @@ 6A187E2816004C5900466B2E /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( - 6A187E2916004C7300466B2E /* zh-Hant */, 6A15B32521A51F2300B92CD3 /* Base */, - 5B9781D92763850700897999 /* zh-Hans */, ); name = MainMenu.xib; sourceTree = ""; @@ -816,6 +826,7 @@ 6ACA41EB15FC1D9000935EF6 /* en */, 6ACA41F515FC1D9000935EF6 /* zh-Hant */, 5B9781D32763850700897999 /* zh-Hans */, + 5BC4BC592796E6A90023BBD5 /* ja */, ); name = InfoPlist.strings; path = Source/Installer; @@ -827,6 +838,7 @@ 6ACA41EF15FC1D9000935EF6 /* en */, 6ACA41F715FC1D9000935EF6 /* zh-Hant */, 5B9781D52763850700897999 /* zh-Hans */, + 5BC4BC5A2796E6A90023BBD5 /* ja */, ); name = Localizable.strings; path = Source/Installer; @@ -839,6 +851,7 @@ 5B19584E27888F5D00FAEB14 /* zh-Hans */, 5B19584F27888F5F00FAEB14 /* zh-Hant */, 5B19585127888F6B00FAEB14 /* en */, + 5BC4BC542796E6A80023BBD5 /* ja */, ); name = MainMenu.xib; path = Source/Installer; -- Gitee From 5bf9246993ce2d3d83cd34756d845cb10a225164 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 18 Jan 2022 23:25:09 +0800 Subject: [PATCH 070/163] Little fixes towards swift code formats. --- Source/AppDelegate.swift | 6 +++--- Source/InputSourceHelper.swift | 16 ++++++++-------- Source/NonModalAlertWindowController.swift | 2 +- Source/PreferencesModule.swift | 4 ++-- Source/PreferencesWindowController.swift | 2 +- .../HorizontalCandidateController.swift | 6 +++--- .../UI/CandidateUI/VTCandidateController.swift | 6 +++--- .../VerticalCandidateController.swift | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 342857e0f..0738f97a3 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -16,7 +16,7 @@ private let kUpdateInfoSiteKey = "UpdateInfoSite" private let kNextCheckInterval: TimeInterval = 86400.0 private let kTimeoutInterval: TimeInterval = 60.0 -@objc (AppDelegate) +@objc(AppDelegate) class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate { @@ -64,12 +64,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, aboutWindowController?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 } - @objc (checkForUpdate) + @objc(checkForUpdate) func checkForUpdate() { checkForUpdate(forced: false) } - @objc (checkForUpdateForced:) + @objc(checkForUpdateForced:) func checkForUpdate(forced: Bool) { if checkTask != nil { diff --git a/Source/InputSourceHelper.swift b/Source/InputSourceHelper.swift index 1715c25f5..4660bf712 100644 --- a/Source/InputSourceHelper.swift +++ b/Source/InputSourceHelper.swift @@ -20,7 +20,7 @@ public class InputSourceHelper: NSObject { TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] } - @objc (inputSourceForProperty:stringValue:) + @objc(inputSourceForProperty:stringValue:) public static func inputSource(for propertyKey: CFString, stringValue: String) -> TISInputSource? { let stringID = CFStringGetTypeID() for source in allInstalledInputSources() { @@ -38,12 +38,12 @@ public class InputSourceHelper: NSObject { return nil } - @objc (inputSourceForInputSourceID:) + @objc(inputSourceForInputSourceID:) public static func inputSource(for sourceID: String) -> TISInputSource? { inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID) } - @objc (inputSourceEnabled:) + @objc(inputSourceEnabled:) public static func inputSourceEnabled(for source: TISInputSource) -> Bool { if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) { let value = Unmanaged.fromOpaque(valuePts).takeUnretainedValue() @@ -52,13 +52,13 @@ public class InputSourceHelper: NSObject { return false } - @objc (enableInputSource:) + @objc(enableInputSource:) public static func enable(inputSource: TISInputSource) -> Bool { let status = TISEnableInputSource(inputSource) return status == noErr } - @objc (enableAllInputModesForInputSourceBundleID:) + @objc(enableAllInputModesForInputSourceBundleID:) public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool { var enabled = false for source in allInstalledInputSources() { @@ -79,7 +79,7 @@ public class InputSourceHelper: NSObject { return enabled } - @objc (enableInputMode:forInputSourceBundleID:) + @objc(enableInputMode:forInputSourceBundleID:) public static func enable(inputMode modeID: String, for bundleID: String) -> Bool { for source in allInstalledInputSources() { guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID), @@ -100,13 +100,13 @@ public class InputSourceHelper: NSObject { } - @objc (disableInputSource:) + @objc(disableInputSource:) public static func disable(inputSource: TISInputSource) -> Bool { let status = TISDisableInputSource(inputSource) return status == noErr } - @objc (registerInputSource:) + @objc(registerInputSource:) public static func registerTnputSource(at url: URL) -> Bool { let status = TISRegisterInputSource(url as CFURL) return status == noErr diff --git a/Source/NonModalAlertWindowController.swift b/Source/NonModalAlertWindowController.swift index e6e369a85..1b79ec8cf 100644 --- a/Source/NonModalAlertWindowController.swift +++ b/Source/NonModalAlertWindowController.swift @@ -14,7 +14,7 @@ import Cocoa } class NonModalAlertWindowController: NSWindowController { - @objc (sharedInstance) + @objc(sharedInstance) static let shared = NonModalAlertWindowController(windowNibName: "NonModalAlertWindowController") diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 406df11b6..779fb1f57 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -220,7 +220,7 @@ struct ComposingBufferSize { @objc static func toogleHalfWidthPunctuationEnabled() -> Bool { halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled - return halfWidthPunctuationEnabled; + return halfWidthPunctuationEnabled } @UserDefault(key: kEscToCleanInputBufferKey, defaultValue: false) @@ -306,6 +306,6 @@ struct ComposingBufferSize { @objc static func tooglePhraseReplacementEnabled() -> Bool { phraseReplacementEnabled = !phraseReplacementEnabled UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabledKey) - return phraseReplacementEnabled; + return phraseReplacementEnabled } } diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index b8a760585..d2f41522b 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -20,7 +20,7 @@ extension RangeReplaceableCollection where Element: Hashable { // Please note that the class should be exposed as "PreferencesWindowController" // in Objective-C in order to let IMK to see the same class name as // the "InputMethodServerPreferencesWindowControllerClass" in Info.plist. -@objc (PreferencesWindowController) class PreferencesWindowController: NSWindowController { +@objc(PreferencesWindowController) class PreferencesWindowController: NSWindowController { @IBOutlet weak var fontSizePopUpButton: NSPopUpButton! @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! @IBOutlet weak var selectionKeyComboBox: NSComboBox! diff --git a/Source/UI/CandidateUI/HorizontalCandidateController.swift b/Source/UI/CandidateUI/HorizontalCandidateController.swift index 1ece2658a..32f7ebd7d 100644 --- a/Source/UI/CandidateUI/HorizontalCandidateController.swift +++ b/Source/UI/CandidateUI/HorizontalCandidateController.swift @@ -42,7 +42,7 @@ fileprivate class HorizontalCandidateView: NSView { return result } - @objc (setKeyLabels:displayedCandidates:) + @objc(setKeyLabels:displayedCandidates:) func set(keyLabels labels: [String], displayedCandidates candidates: [String]) { let count = min(labels.count, candidates.count) keyLabels = Array(labels[0.. UInt func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) } -@objc (VTCandidateController) +@objc(VTCandidateController) public class CandidateController: NSWindowController { @objc public weak var delegate: CandidateControllerDelegate? @objc public var selectedCandidateIndex: UInt = UInt.max @@ -69,7 +69,7 @@ public class CandidateController: NSWindowController { UInt.max } - @objc (setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) + @objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height) diff --git a/Source/UI/CandidateUI/VerticalCandidateController.swift b/Source/UI/CandidateUI/VerticalCandidateController.swift index b57a78b7c..87aa31fa5 100644 --- a/Source/UI/CandidateUI/VerticalCandidateController.swift +++ b/Source/UI/CandidateUI/VerticalCandidateController.swift @@ -71,7 +71,7 @@ private let kCandidateTextLeftMargin:CGFloat = 8.0 -@objc (VTVerticalCandidateController) +@objc(VTVerticalCandidateController) public class VerticalCandidateController: CandidateController { private var keyLabelStripView: VerticalKeyLabelStripView private var scrollView: NSScrollView -- Gitee From 8f97f7c5181b37a69e008470e45985ad09fdc74c Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 18 Jan 2022 23:28:45 +0800 Subject: [PATCH 071/163] Zonble: NSLog text update regarding line-height rectangle. --- Source/InputMethodController.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 89f642719..0905c5c1e 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1341,7 +1341,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; } @catch (NSException *exception) { - NSLog(@"%@", exception); + NSLog(@"lineHeightRectangle %@", exception); } if (useVerticalMode) { @@ -1448,7 +1448,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; } @catch (NSException *exception) { - NSLog(@"%@", exception); + NSLog(@"lineHeightRectangle %@", exception); } [[vChewingInputMethodController tooltipController] showTooltip:tooltip atPoint:lineHeightRect.origin]; -- Gitee From 671aed70de8f8f11c688aad6259decc1d211bf3b Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 18 Jan 2022 23:49:14 +0800 Subject: [PATCH 072/163] Zonble: Tooltip UI // multiple bug fixes. - Stop Tooltip UI from running out of the screen. - Discourage user-phrase when replacement is ON. - Typo Fix. --- Source/InputMethodController.mm | 12 ++++-- Source/UI/TooltipUI/TooltipController.swift | 45 ++++++++++++++++++--- Source/en.lproj/Localizable.strings | 1 + Source/ja.lproj/Localizable.strings | 1 + Source/zh-Hans.lproj/Localizable.strings | 1 + Source/zh-Hant.lproj/Localizable.strings | 1 + 6 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 0905c5c1e..7c04ade75 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1424,13 +1424,17 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (!length) { [self _hideTooltip]; } + else if (Preferences.phraseReplacementEnabled) { + NSString *message = [NSString stringWithFormat:NSLocalizedString(@"⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", @""), text]; + [self _showTooltip:message client:client]; + } else if (length == 1) { - NSString *messsage = [NSString stringWithFormat:NSLocalizedString(@"\"%@\" length must ≥ 2 for a user phrase.", @""), text]; - [self _showTooltip:messsage client:client]; + NSString *message = [NSString stringWithFormat:NSLocalizedString(@"\"%@\" length must ≥ 2 for a user phrase.", @""), text]; + [self _showTooltip:message client:client]; } else { - NSString *messsage = [NSString stringWithFormat:NSLocalizedString(@"\"%@\" selected. ENTER to add user phrase.", @""), text]; - [self _showTooltip:messsage client:client]; + NSString *message = [NSString stringWithFormat:NSLocalizedString(@"\"%@\" selected. ENTER to add user phrase.", @""), text]; + [self _showTooltip:message client:client]; } } diff --git a/Source/UI/TooltipUI/TooltipController.swift b/Source/UI/TooltipUI/TooltipController.swift index 8d9013c4e..7301f7ba7 100644 --- a/Source/UI/TooltipUI/TooltipController.swift +++ b/Source/UI/TooltipUI/TooltipController.swift @@ -54,12 +54,47 @@ public class TooltipController: NSWindowController { window?.orderOut(nil) } - private func set(windowLocation location: NSPoint) { - var newPoint = location - if location.y > 5 { - newPoint.y -= 5 + private func set(windowLocation windowTopLeftPoint: NSPoint) { + + var adjustedPoint = windowTopLeftPoint + adjustedPoint.y -= 5 + + var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero + for screen in NSScreen.screens { + let frame = screen.visibleFrame + if windowTopLeftPoint.x >= frame.minX && + windowTopLeftPoint.x <= frame.maxX && + windowTopLeftPoint.y >= frame.minY && + windowTopLeftPoint.y <= frame.maxY { + screenFrame = frame + break + } + } + + let windowSize = window?.frame.size ?? NSSize.zero + + // bottom beneath the screen? + if adjustedPoint.y - windowSize.height < screenFrame.minY { + adjustedPoint.y = screenFrame.minY + windowSize.height + } + + // top over the screen? + if adjustedPoint.y >= screenFrame.maxY { + adjustedPoint.y = screenFrame.maxY - 1.0 } - window?.setFrameTopLeftPoint(newPoint) + + // right + if adjustedPoint.x + windowSize.width >= screenFrame.maxX { + adjustedPoint.x = screenFrame.maxX - windowSize.width + } + + // left + if adjustedPoint.x < screenFrame.minX { + adjustedPoint.x = screenFrame.minX + } + + window?.setFrameTopLeftPoint(adjustedPoint) + } private func adjustSize() { diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 6f67322e1..15c2a383b 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -32,3 +32,4 @@ "There should not be duplicated keys." = "There should not be duplicated keys."; "Please specify at least 4 candidate keys." = "Please specify at least 4 candidate keys."; "Maximum 15 candidate keys allowed." = "Maximum 15 candidate keys allowed."; +"⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ Phrase replacement mode enabled, interfering user phrase entry."; diff --git a/Source/ja.lproj/Localizable.strings b/Source/ja.lproj/Localizable.strings index 5afd2ab9a..f25d1d801 100644 --- a/Source/ja.lproj/Localizable.strings +++ b/Source/ja.lproj/Localizable.strings @@ -32,3 +32,4 @@ "There should not be duplicated keys." = "言選り用キー陣列に同じキーの重複登録はできません。"; "Please specify at least 4 candidate keys." = "言選り用キー陣列に少なくとも4つのキーをご登録ください。"; "Maximum 15 candidate keys allowed." = "言選り用キー陣列には最多15つキー登録できます。"; +"⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 言葉置換機能稼働中、新添付言葉にも影響。"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index f88e98674..b96478e50 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -32,3 +32,4 @@ "There should not be duplicated keys." = "选字键不得重复。"; "Please specify at least 4 candidate keys." = "请至少指定四个选字键。"; "Maximum 15 candidate keys allowed." = "选字键最多只能指定十五个。"; +"⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 语汇置换功能已启用,会波及语汇自订。"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 3ef845c79..a37dfe0c5 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -32,3 +32,4 @@ "There should not be duplicated keys." = "選字鍵不得重複。"; "Please specify at least 4 candidate keys." = "請至少指定四個選字鍵。"; "Maximum 15 candidate keys allowed." = "選字鍵最多只能指定十五個。"; +"⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 語彙置換功能已啟用,會波及語彙自訂。"; -- Gitee From cf9cec86b5960ae4524db3d35811640aa73375bf Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 00:37:31 +0800 Subject: [PATCH 073/163] Zonble: Revampled AppDelegate. --- Source/AppDelegate.swift | 258 ++++++++++++++++++++++----------------- 1 file changed, 148 insertions(+), 110 deletions(-) diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 0738f97a3..d334a23be 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -16,16 +16,126 @@ private let kUpdateInfoSiteKey = "UpdateInfoSite" private let kNextCheckInterval: TimeInterval = 86400.0 private let kTimeoutInterval: TimeInterval = 60.0 +struct VersionUpdateReport { + var siteUrl: URL? + var currentShortVersion: String = "" + var currentVersion: String = "" + var remoteShortVersion: String = "" + var remoteVersion: String = "" + var versionDescription: String = "" +} + +enum VersionUpdateApiResult { + case shouldUpdate(report: VersionUpdateReport) + case noNeedToUpdate + case ignored +} + +enum VersionUpdateApiError: Error, LocalizedError { + case connectionError(message: String) + + var errorDescription: String? { + switch self { + case .connectionError(let message): + return String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message) + } + } +} + +struct VersionUpdateApi { + static func check(forced: Bool, callback: @escaping (Result) -> ()) -> URLSessionTask? { + guard let infoDict = Bundle.main.infoDictionary, + let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, + let updateInfoURL = URL(string: updateInfoURLString) else { + return nil + } + + let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval) + let task = URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + DispatchQueue.main.async { + forced ? + callback(.failure(VersionUpdateApiError.connectionError(message: error.localizedDescription))) : + callback(.success(.ignored)) + } + return + } + + do { + guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], + let remoteVersion = plist[kCFBundleVersionKey] as? String, + let infoDict = Bundle.main.infoDictionary + else { + DispatchQueue.main.async { + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) + } + return + } + + // TODO: Validate info (e.g. bundle identifier) + // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this + let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" + let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil) + + if result != .orderedAscending { + DispatchQueue.main.async { + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) + } + return + } + + guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, + let siteInfoURL = URL(string: siteInfoURLString) + else { + DispatchQueue.main.async { + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) + } + return + } + + var report = VersionUpdateReport(siteUrl: siteInfoURL) + var versionDescription = "" + let versionDescriptions = plist["Description"] as? [AnyHashable: Any] + if let versionDescriptions = versionDescriptions { + var locale = "en" + let supportedLocales = ["en", "zh-Hant", "zh-Hans", "ja"] + let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) + if let first = preferredTags.first { + locale = first + } + versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? "" + if !versionDescription.isEmpty { + versionDescription = "\n\n" + versionDescription + } + } + report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? "" + report.currentVersion = currentVersion + report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? "" + report.remoteVersion = remoteVersion + report.versionDescription = versionDescription + DispatchQueue.main.async { + callback(.success(.shouldUpdate(report: report))) + } + } catch { + DispatchQueue.main.async { + forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored)) + } + } + } + task.resume() + return task + } +} + @objc(AppDelegate) -class AppDelegate: NSObject, NSApplicationDelegate, - NonModalAlertWindowControllerDelegate { - +class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate { + @IBOutlet weak var window: NSWindow? private var preferencesWindowController: PreferencesWindowController? private var aboutWindowController: frmAboutWindow? // New About Window private var checkTask: URLSessionTask? private var updateNextStepURL: URL? - + // 補上 dealloc deinit { preferencesWindowController = nil @@ -33,12 +143,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, checkTask = nil updateNextStepURL = nil } - + func applicationDidFinishLaunching(_ notification: Notification) { LanguageModelManager.loadDataModels() LanguageModelManager.loadUserPhrases() LanguageModelManager.loadUserPhraseReplacement() - + OOBE.setMissingDefaults() // 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。 @@ -46,7 +156,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, checkForUpdate() } } - + @objc func showPreferences() { if (preferencesWindowController == nil) { preferencesWindowController = PreferencesWindowController(windowNibName: "preferences") @@ -63,20 +173,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, aboutWindowController?.window?.center() aboutWindowController?.window?.orderFrontRegardless() // 逼著關於視窗往最前方顯示 } - + @objc(checkForUpdate) func checkForUpdate() { checkForUpdate(forced: false) } - + @objc(checkForUpdateForced:) func checkForUpdate(forced: Bool) { - + if checkTask != nil { // busy return } - + // time for update? if !forced { if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false { @@ -88,122 +198,50 @@ class AppDelegate: NSObject, NSApplicationDelegate, return } } - + let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date()) UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey) - - guard let infoDict = Bundle.main.infoDictionary, - let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String, - let updateInfoURL = URL(string: updateInfoURLString) else { - return - } - - let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval) - - func showNoUpdateAvailableAlert() { - NonModalAlertWindowController.shared.show(title: NSLocalizedString("Check for Update Completed", comment: ""), content: NSLocalizedString("You are already using the latest version of vChewing.", comment: ""), confirmButtonTitle: NSLocalizedString("OK", comment: ""), cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) - } - - let task = URLSession.shared.dataTask(with: request) { data, response, error in + + checkTask = VersionUpdateApi.check(forced: forced) { result in defer { self.checkTask = nil } - - if let error = error { - if forced { + switch result { + case .success(let apiResult): + switch apiResult { + case .shouldUpdate(let report): + self.updateNextStepURL = report.siteUrl + let content = String(format: NSLocalizedString("You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", comment: ""), + report.currentShortVersion, + report.currentVersion, + report.remoteShortVersion, + report.remoteVersion, + report.versionDescription) + NonModalAlertWindowController.shared.show(title: NSLocalizedString("New Version Available", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self) + case .noNeedToUpdate, .ignored: + break + } + case .failure(let error): + switch error { + case VersionUpdateApiError.connectionError(let message): let title = NSLocalizedString("Update Check Failed", comment: "") - let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), error.localizedDescription) + let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message) let buttonTitle = NSLocalizedString("Dismiss", comment: "") - - DispatchQueue.main.async { - NonModalAlertWindowController.shared.show(title: title , content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) - } - } - return - } - - do { - guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any], - let remoteVersion = plist[kCFBundleVersionKey] as? String, - let infoDict = Bundle.main.infoDictionary - else { - if forced { - DispatchQueue.main.async { - showNoUpdateAvailableAlert() - } - } - return - } - - // TODO: Validate info (e.g. bundle identifier) - // TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this - let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? "" - let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil) - - if result != .orderedAscending { - if forced { - DispatchQueue.main.async { - showNoUpdateAvailableAlert() - } - } - } - - guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String, - let siteInfoURL = URL(string: siteInfoURLString) else { - if forced { - DispatchQueue.main.async { - showNoUpdateAvailableAlert() - } - } - return - } - - self.updateNextStepURL = siteInfoURL - - var versionDescription = "" - let versionDescriptions = plist["Description"] as? [AnyHashable: Any] - if let versionDescriptions = versionDescriptions { - var locale = "en" - let supportedLocales = ["en", "zh-Hant", "zh-Hans"] - let preferredTags = Bundle.preferredLocalizations(from: supportedLocales) - if let first = preferredTags.first { - locale = first - } - versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? "" - if !versionDescription.isEmpty { - versionDescription = "\n\n" + versionDescription - } - } - - let content = String(format: NSLocalizedString("You're currently using vChewing %@ (%@), a new version %@ (%@) is now available. Do you want to visit vChewing's website to download the version?%@", comment: ""), - infoDict["CFBundleShortVersionString"] as? String ?? "", - currentVersion, - plist["CFBundleShortVersionString"] as? String ?? "", - remoteVersion, - versionDescription) - DispatchQueue.main.async { - NonModalAlertWindowController.shared.show(title: NSLocalizedString("New Version Available", comment: "") , content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self) - } - - } catch { - if forced { - DispatchQueue.main.async { - showNoUpdateAvailableAlert() - } + NonModalAlertWindowController.shared.show(title: title, content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil) + default: + break } } } - checkTask = task - task.resume() } - + func nonModalAlertWindowControllerDidConfirm(_ controller: NonModalAlertWindowController) { if let updateNextStepURL = updateNextStepURL { NSWorkspace.shared.open(updateNextStepURL) } updateNextStepURL = nil } - + func nonModalAlertWindowControllerDidCancel(_ controller: NonModalAlertWindowController) { updateNextStepURL = nil } -- Gitee From 1204a595a23dea74e6d0699e21aa80142ddf892e Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 00:40:54 +0800 Subject: [PATCH 074/163] Zonble: Reload cand. data when CandidateControllerDelegate did set --- .../UI/CandidateUI/VTCandidateController.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Source/UI/CandidateUI/VTCandidateController.swift b/Source/UI/CandidateUI/VTCandidateController.swift index 6af0f20bc..be334f25c 100644 --- a/Source/UI/CandidateUI/VTCandidateController.swift +++ b/Source/UI/CandidateUI/VTCandidateController.swift @@ -17,7 +17,11 @@ public protocol CandidateControllerDelegate: AnyObject { @objc(VTCandidateController) public class CandidateController: NSWindowController { - @objc public weak var delegate: CandidateControllerDelegate? + @objc public weak var delegate: CandidateControllerDelegate? { + didSet { + reloadData() + } + } @objc public var selectedCandidateIndex: UInt = UInt.max @objc public var visible: Bool = false { didSet { @@ -69,6 +73,16 @@ public class CandidateController: NSWindowController { UInt.max } + /// Sets the location of the candidate window. + /// + /// Please note that the method has side effects that modifies + /// `windowTopLeftPoint` to make the candidate window to stay in at least + /// in a screen. + /// + /// - Parameters: + /// - windowTopLeftPoint: The given location. + /// - height: The height that helps the window not to be out of the bottom + /// of a screen. @objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:) public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { -- Gitee From 610b0aa9508ef60dbc99156af5860a14426b6987 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 00:41:34 +0800 Subject: [PATCH 075/163] Zonble: Add a static func to batch clean user plist settings. --- Source/PreferencesModule.swift | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 779fb1f57..77a923086 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -163,6 +163,29 @@ struct ComposingBufferSize { // MARK: - @objc public class Preferences: NSObject { + static func reset() { + let defaults = UserDefaults.standard + defaults.removeObject(forKey: kKeyboardLayoutPreferenceKey) + defaults.removeObject(forKey: kBasisKeyboardLayoutPreferenceKey) + defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutPreferenceKey) + defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey) + defaults.removeObject(forKey: kCandidateListTextSizeKey) + defaults.removeObject(forKey: kSelectPhraseAfterCursorAsCandidatePreferenceKey) + defaults.removeObject(forKey: kUseHorizontalCandidateListPreferenceKey) + defaults.removeObject(forKey: kComposingBufferSizePreferenceKey) + defaults.removeObject(forKey: kChooseCandidateUsingSpaceKey) + defaults.removeObject(forKey: kChineseConversionEnabledKey) + defaults.removeObject(forKey: kHalfWidthPunctuationEnabledKey) + defaults.removeObject(forKey: kEscToCleanInputBufferKey) + defaults.removeObject(forKey: kCandidateTextFontName) + defaults.removeObject(forKey: kCandidateKeyLabelFontName) + defaults.removeObject(forKey: kCandidateKeys) + defaults.removeObject(forKey: kPhraseReplacementEnabledKey) + defaults.removeObject(forKey: kChineseConversionEngineKey) + defaults.removeObject(forKey: kUseWinNT351BPMF) + defaults.removeObject(forKey: kShouldNotFartInLieuOfBeep) + } + @UserDefault(key: kKeyboardLayoutPreferenceKey, defaultValue: 0) @objc static var keyboardLayout: Int -- Gitee From 8429c73ad7b56252f3234f9e2f1038625c707806 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 00:42:11 +0800 Subject: [PATCH 076/163] Typo fix: Toogle -> Toggle. --- Source/InputMethodController.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 7c04ade75..911d949b1 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1493,13 +1493,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-result" - [Preferences toogleHalfWidthPunctuationEnabled]; + [Preferences toggleHalfWidthPunctuationEnabled]; #pragma GCC diagnostic pop } - (void)togglePhraseReplacementEnabled:(id)sender { - BOOL enabled = [Preferences tooglePhraseReplacementEnabled]; + BOOL enabled = [Preferences togglePhraseReplacementEnabled]; vChewingLM *lm = [LanguageModelManager languageModelCoreCHT]; lm->setPhraseReplacementEnabled(enabled); } -- Gitee From 3319167f9fb0f2495567a99be5ee3206bf40d5ab Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 00:42:28 +0800 Subject: [PATCH 077/163] Swift format tweaks. --- Source/PreferencesModule.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 77a923086..15388c713 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -9,9 +9,9 @@ import Cocoa private let kKeyboardLayoutPreferenceKey = "KeyboardLayout" -private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout"; // alphanumeric ("ASCII") input basi -private let kFunctionKeyKeyboardLayoutPreferenceKey = "FunctionKeyKeyboardLayout"; // alphanumeric ("ASCII") input basi -private let kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = "FunctionKeyKeyboardLayoutOverrideIncludeShift"; // whether include shif +private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout" // alphanumeric ("ASCII") input basi +private let kFunctionKeyKeyboardLayoutPreferenceKey = "FunctionKeyKeyboardLayout" // alphanumeric ("ASCII") input basi +private let kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = "FunctionKeyKeyboardLayoutOverrideIncludeShift" // whether include shif private let kCandidateListTextSizeKey = "CandidateListTextSize" private let kSelectPhraseAfterCursorAsCandidatePreferenceKey = "SelectPhraseAfterCursorAsCandidate" private let kUseHorizontalCandidateListPreferenceKey = "UseHorizontalCandidateList" @@ -36,8 +36,8 @@ private let kMaxCandidateListTextSize: CGFloat = 196 // default, min and max composing buffer size (in codepoints) // modern Macs can usually work up to 16 codepoints when the builder still -// walks the grid with good performance; slower Macs (like old PowerBooks) -// will start to sputter beyond 12; such is the algorithmatic complexity +// walks the grid with good performance slower Macs (like old PowerBooks) +// will start to sputter beyond 12 such is the algorithmatic complexity // of the Viterbi algorithm used in the builder library (at O(N^2)) private let kDefaultComposingBufferSize = 10 private let kMinComposingBufferSize = 4 @@ -241,7 +241,7 @@ struct ComposingBufferSize { @UserDefault(key: kHalfWidthPunctuationEnabledKey, defaultValue: false) @objc static var halfWidthPunctuationEnabled: Bool - @objc static func toogleHalfWidthPunctuationEnabled() -> Bool { + @objc static func toggleHalfWidthPunctuationEnabled() -> Bool { halfWidthPunctuationEnabled = !halfWidthPunctuationEnabled return halfWidthPunctuationEnabled } @@ -266,7 +266,7 @@ struct ComposingBufferSize { [kDefaultKeys, "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB"] } - static func validate(candidateKeys: String) throws { + @objc static func validate(candidateKeys: String) throws { let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty { throw CandidateKeyError.empty @@ -326,7 +326,7 @@ struct ComposingBufferSize { @UserDefault(key: kPhraseReplacementEnabledKey, defaultValue: false) @objc static var phraseReplacementEnabled: Bool - @objc static func tooglePhraseReplacementEnabled() -> Bool { + @objc static func togglePhraseReplacementEnabled() -> Bool { phraseReplacementEnabled = !phraseReplacementEnabled UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabledKey) return phraseReplacementEnabled -- Gitee From 3a379747297215f3960877e7d56c05fea905796b Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 01:04:20 +0800 Subject: [PATCH 078/163] Enable support of maximum candidate length = 10 --- Source/Engine/Gramambular/BlockReadingBuilder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index b4c5bcc4e..af4e4b97a 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -46,7 +46,7 @@ namespace Taiyan { static const string Join(vector::const_iterator begin, vector::const_iterator end, const string& separator); //最多使用六個字組成一個詞 - static const size_t MaximumBuildSpanLength = 6; + static const size_t MaximumBuildSpanLength = 10; size_t m_cursorIndex; size_t m_markerCursorIndex; -- Gitee From 7d45b0c03a68034992553b909943f20e39b95e6b Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 08:52:19 +0800 Subject: [PATCH 079/163] User data tolerance marking (no actual changes) --- Source/Engine/LanguageModel/UserPhrasesLM.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.cpp b/Source/Engine/LanguageModel/UserPhrasesLM.cpp index 6d7ee6e1b..583e9a4ab 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.cpp +++ b/Source/Engine/LanguageModel/UserPhrasesLM.cpp @@ -63,9 +63,9 @@ bool UserPhrasesLM::open(const char *path) KeyValueBlobReader::State state; while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) { // We invert the key and value, since in user phrases, "key" is the phrase value, and "value" is the BPMF reading. - keyRowMap[keyValue.value].emplace_back(keyValue.value, keyValue.key ); + keyRowMap[keyValue.value].emplace_back(keyValue.value, keyValue.key); } - + // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) if (state == KeyValueBlobReader::State::ERROR) { close(); return false; -- Gitee From 75c99a10da25eaf20d5581e15fa4bf195655c893 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 11:00:05 +0800 Subject: [PATCH 080/163] Shiki: Attempt to separate CHS / CHT lang models (buggy). - bug needs further fix: UserDict entries are duplicated in the Candidate List. Note that this bug was introduced by someone else's script from upstream. --- Source/InputMethodController.h | 3 - Source/InputMethodController.mm | 49 ++++++++++---- Source/LanguageModelManager.h | 16 +++-- Source/LanguageModelManager.mm | 116 +++++++++++++++++++++++++------- 4 files changed, 138 insertions(+), 46 deletions(-) diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index ac0269d61..c72236dda 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -32,9 +32,6 @@ // latest walked path (trellis) using the Viterbi algorithm std::vector _walkedNodes; - // user override model - vChewing::UserOverrideModel *_uom; - // the latest composing buffer that is updated to the foreground app NSMutableString *_composingBuffer; NSInteger _latestReadingCursor; diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 911d949b1..8bc7f1ba6 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -112,7 +112,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) // create the lattice builder _languageModel = [LanguageModelManager languageModelCoreCHT]; _languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); - _userOverrideModel = [LanguageModelManager userOverrideModel]; + _userOverrideModel = [LanguageModelManager userOverrideModelCHT]; _builder = new BlockReadingBuilder(_languageModel); @@ -243,13 +243,16 @@ static double FindHighestScore(const vector& nodes, double epsilon) { NSString *newInputMode; vChewingLM *newLanguageModel; + UserOverrideModel *newUserOverrideModel; if ([value isKindOfClass:[NSString class]] && [value isEqual:kBopomofoModeIdentifierCHS]) { newInputMode = kBopomofoModeIdentifierCHS; newLanguageModel = [LanguageModelManager languageModelCoreCHS]; + newUserOverrideModel = [LanguageModelManager userOverrideModelCHS]; } else { newInputMode = kBopomofoModeIdentifierCHT; newLanguageModel = [LanguageModelManager languageModelCoreCHT]; + newUserOverrideModel = [LanguageModelManager userOverrideModelCHT]; } // 自 Preferences 模組讀入自訂語彙置換功能開關狀態。 @@ -265,6 +268,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) _inputMode = newInputMode; _languageModel = newLanguageModel; + _userOverrideModel = newUserOverrideModel; if (!_bpmfReadingBuffer->isEmpty()) { _bpmfReadingBuffer->clear(); @@ -1413,8 +1417,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } if (![currentMarkedPhrase length]) { return NO; } - - return [LanguageModelManager writeUserPhrase:currentMarkedPhrase]; + + if (_inputMode == kBopomofoModeIdentifierCHT) { + return [LanguageModelManager writeUserPhraseCHT:currentMarkedPhrase]; + } else { + return [LanguageModelManager writeUserPhraseCHS:currentMarkedPhrase]; + } } - (void)_showCurrentMarkedTextTooltipWithClient:(id)client @@ -1499,9 +1507,15 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)togglePhraseReplacementEnabled:(id)sender { - BOOL enabled = [Preferences togglePhraseReplacementEnabled]; - vChewingLM *lm = [LanguageModelManager languageModelCoreCHT]; - lm->setPhraseReplacementEnabled(enabled); + if (_inputMode == kBopomofoModeIdentifierCHT) { + BOOL enabled = [Preferences togglePhraseReplacementEnabled]; + vChewingLM *lm = [LanguageModelManager languageModelCoreCHT]; + lm->setPhraseReplacementEnabled(enabled); + } else { + BOOL enabled = [Preferences togglePhraseReplacementEnabled]; + vChewingLM *lm = [LanguageModelManager languageModelCoreCHS]; + lm->setPhraseReplacementEnabled(enabled); + } } - (void)checkForUpdate:(id)sender @@ -1531,22 +1545,29 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)openUserPhrases:(id)sender { - [self _openUserFile:[LanguageModelManager userPhrasesDataPathBopomofo]]; -} - -- (void)openExcludedPhrasesSimpBopomofo:(id)sender -{ - [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathSimpBopomofo]]; + if (_inputMode == kBopomofoModeIdentifierCHT) { + [self _openUserFile:[LanguageModelManager userPhrasesDataPathCHT]]; + } else { + [self _openUserFile:[LanguageModelManager userPhrasesDataPathCHS]]; + } } - (void)openExcludedPhrases:(id)sender { - [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathBopomofo]]; + if (_inputMode == kBopomofoModeIdentifierCHT) { + [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathCHT]]; + } else { + [self _openUserFile:[LanguageModelManager excludedPhrasesDataPathCHS]]; + } } - (void)openPhraseReplacement:(id)sender { - [self _openUserFile:[LanguageModelManager phraseReplacementDataPathBopomofo]]; + if (_inputMode == kBopomofoModeIdentifierCHT) { + [self _openUserFile:[LanguageModelManager phraseReplacementDataPathCHT]]; + } else { + [self _openUserFile:[LanguageModelManager phraseReplacementDataPathCHS]]; + } } - (void)reloadUserPhrases:(id)sender diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index aa9a6f044..d68b605c1 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -19,16 +19,20 @@ NS_ASSUME_NONNULL_BEGIN + (void)loadUserPhrases; + (void)loadUserPhraseReplacement; + (BOOL)checkIfUserLanguageModelFilesExist; -+ (BOOL)writeUserPhrase:(NSString *)userPhrase; ++ (BOOL)writeUserPhraseCHT:(NSString *)userPhraseCHT; ++ (BOOL)writeUserPhraseCHS:(NSString *)userPhraseCHS; @property (class, readonly, nonatomic) NSString *dataFolderPath; -@property (class, readonly, nonatomic) NSString *userPhrasesDataPathBopomofo; -@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathBopomofo; -@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathSimpBopomofo; -@property (class, readonly, nonatomic) NSString *phraseReplacementDataPathBopomofo; +@property (class, readonly, nonatomic) NSString *userPhrasesDataPathCHT; +@property (class, readonly, nonatomic) NSString *userPhrasesDataPathCHS; +@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathCHT; +@property (class, readonly, nonatomic) NSString *excludedPhrasesDataPathCHS; +@property (class, readonly, nonatomic) NSString *phraseReplacementDataPathCHT; +@property (class, readonly, nonatomic) NSString *phraseReplacementDataPathCHS; @property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelCoreCHT; @property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelCoreCHS; -@property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModel; +@property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHT; +@property (class, readonly, nonatomic) vChewing::UserOverrideModel *userOverrideModelCHS; @end NS_ASSUME_NONNULL_END diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index da9688173..b7118dc1d 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -23,7 +23,8 @@ static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. vChewingLM glanguageModelCoreCHT; vChewingLM glanguageModelCoreCHS; -UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); +UserOverrideModel gUserOverrideModelCHS(kUserOverrideModelCapacity, kObservedOverrideHalflife); +UserOverrideModel gUserOverrideModelCHT(kUserOverrideModelCapacity, kObservedOverrideHalflife); @implementation LanguageModelManager @@ -42,13 +43,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (void)loadUserPhrases { - glanguageModelCoreCHT.loadUserPhrases([[self userPhrasesDataPathBopomofo] UTF8String], [[self excludedPhrasesDataPathBopomofo] UTF8String]); - glanguageModelCoreCHS.loadUserPhrases(NULL, [[self excludedPhrasesDataPathSimpBopomofo] UTF8String]); + glanguageModelCoreCHT.loadUserPhrases([[self userPhrasesDataPathCHT] UTF8String], [[self excludedPhrasesDataPathCHT] UTF8String]); + glanguageModelCoreCHS.loadUserPhrases([[self userPhrasesDataPathCHS] UTF8String], [[self excludedPhrasesDataPathCHS] UTF8String]); } + (void)loadUserPhraseReplacement { - glanguageModelCoreCHT.loadPhraseReplacementMap([[self phraseReplacementDataPathBopomofo] UTF8String]); + glanguageModelCoreCHT.loadPhraseReplacementMap([[self phraseReplacementDataPathCHT] UTF8String]); + glanguageModelCoreCHS.loadPhraseReplacementMap([[self phraseReplacementDataPathCHS] UTF8String]); } + (BOOL)checkIfUserDataFolderExists @@ -93,29 +95,35 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing if (![self checkIfUserDataFolderExists]) { return NO; } - if (![self checkIfFileExist:[self userPhrasesDataPathBopomofo]]) { + if (![self checkIfFileExist:[self userPhrasesDataPathCHT]]) { return NO; } - if (![self checkIfFileExist:[self excludedPhrasesDataPathBopomofo]]) { + if (![self checkIfFileExist:[self excludedPhrasesDataPathCHT]]) { return NO; } - if (![self checkIfFileExist:[self excludedPhrasesDataPathSimpBopomofo]]) { + if (![self checkIfFileExist:[self phraseReplacementDataPathCHT]]) { return NO; } - if (![self checkIfFileExist:[self phraseReplacementDataPathBopomofo]]) { - return NO; - } - return YES; + if (![self checkIfFileExist:[self userPhrasesDataPathCHS]]) { + return NO; + } + if (![self checkIfFileExist:[self excludedPhrasesDataPathCHS]]) { + return NO; + } + if (![self checkIfFileExist:[self phraseReplacementDataPathCHS]]) { + return NO; + } + return YES; } -+ (BOOL)writeUserPhrase:(NSString *)userPhrase ++ (BOOL)writeUserPhraseCHT:(NSString *)userPhrase { if (![self checkIfUserLanguageModelFilesExist]) { return NO; } BOOL shuoldAddLineBreakAtFront = NO; - NSString *path = [self userPhrasesDataPathBopomofo]; + NSString *path = [self userPhrasesDataPathCHT]; if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { NSError *error = nil; @@ -155,6 +163,53 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return YES; } ++ (BOOL)writeUserPhraseCHS:(NSString *)userPhrase +{ + if (![self checkIfUserLanguageModelFilesExist]) { + return NO; + } + + BOOL shuoldAddLineBreakAtFront = NO; + NSString *path = [self userPhrasesDataPathCHS]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + NSError *error = nil; + NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; + unsigned long long fileSize = [attr fileSize]; + if (!error && fileSize) { + NSFileHandle *readFile = [NSFileHandle fileHandleForReadingAtPath:path]; + if (readFile) { + [readFile seekToFileOffset:fileSize - 1]; + NSData *data = [readFile readDataToEndOfFile]; + const void *bytes = [data bytes]; + if (*(char *)bytes != '\n') { + shuoldAddLineBreakAtFront = YES; + } + [readFile closeFile]; + } + } + } + + NSMutableString *currentMarkedPhrase = [NSMutableString string]; + if (shuoldAddLineBreakAtFront) { + [currentMarkedPhrase appendString:@"\n"]; + } + [currentMarkedPhrase appendString:userPhrase]; + [currentMarkedPhrase appendString:@"\n"]; + + NSFileHandle *writeFile = [NSFileHandle fileHandleForUpdatingAtPath:path]; + if (!writeFile) { + return NO; + } + [writeFile seekToEndOfFile]; + NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding]; + [writeFile writeData:data]; + [writeFile closeFile]; + + [self loadUserPhrases]; + return YES; +} + + (NSString *)dataFolderPath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES); @@ -163,24 +218,34 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return userDictPath; } -+ (NSString *)userPhrasesDataPathBopomofo ++ (NSString *)userPhrasesDataPathCHT +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"userdata-cht.txt"]; +} + ++ (NSString *)userPhrasesDataPathCHS +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"userdata-chs.txt"]; +} + ++ (NSString *)excludedPhrasesDataPathCHT { - return [[self dataFolderPath] stringByAppendingPathComponent:@"data-cht.txt"]; + return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-cht.txt"]; } -+ (NSString *)excludedPhrasesDataPathBopomofo ++ (NSString *)excludedPhrasesDataPathCHS { - return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases.txt"]; + return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-chs.txt"]; } -+ (NSString *)excludedPhrasesDataPathSimpBopomofo ++ (NSString *)phraseReplacementDataPathCHT { - return [[self dataFolderPath] stringByAppendingPathComponent:@"exclude-phrases-plain-bpmf.txt"]; + return [[self dataFolderPath] stringByAppendingPathComponent:@"phrases-replacement-cht.txt"]; } -+ (NSString *)phraseReplacementDataPathBopomofo ++ (NSString *)phraseReplacementDataPathCHS { - return [[self dataFolderPath] stringByAppendingPathComponent:@"phrases-replacement.txt"]; + return [[self dataFolderPath] stringByAppendingPathComponent:@"phrases-replacement-chs.txt"]; } + (vChewingLM *)languageModelCoreCHT @@ -193,9 +258,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return &glanguageModelCoreCHS; } -+ (vChewing::UserOverrideModel *)userOverrideModel ++ (vChewing::UserOverrideModel *)userOverrideModelCHT +{ + return &gUserOverrideModelCHT; +} + ++ (vChewing::UserOverrideModel *)userOverrideModelCHS { - return &gUserOverrideModel; + return &gUserOverrideModelCHS; } @end -- Gitee From 8be06a89d5e13bc04ee6fc3b0fc86a457a0cc73a Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 12:07:24 +0800 Subject: [PATCH 081/163] Fixed a regression of KangXi mode hotkey failure. - Previously, the KangXi mode switch was actually not inserted to the menu in the CHS mode when I was trying to just hide it. This leads to the defect of the hotkey used for toggling the KangXi mode. This feature needs will be dealt later. Fixed a regression of KangXi mode hotkey failure. // p2 --- Source/InputMethodController.mm | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 8bc7f1ba6..102230d47 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -138,11 +138,9 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItemWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; - if ((_inputMode == kBopomofoModeIdentifierCHT) && optionKeyPressed) { - NSMenuItem *chineseConversionMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Force KangXi Writing", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; + NSMenuItem *chineseConversionMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Force KangXi Writing", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; - } NSMenuItem *halfWidthPunctuationMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""]; halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; @@ -1491,10 +1489,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)toggleChineseConverter:(id)sender { - if (_inputMode == kBopomofoModeIdentifierCHT) { - BOOL chineseConversionEnabled = [Preferences toggleChineseConversionEnabled]; - [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Force KangXi Writing", @""), @"\n", chineseConversionEnabled ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; - } + [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Force KangXi Writing", @""), @"\n", [Preferences toggleChineseConversionEnabled] ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; } - (void)toggleHalfWidthPunctuation:(id)sender -- Gitee From 57ecd7b5c39a5b31fecb0ca53d697278b72d3b13 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 17:46:45 +0800 Subject: [PATCH 082/163] Phrase rep. map tolerance marking (no actual changes) --- Source/Engine/LanguageModel/PhraseReplacementMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp index fb6900cc0..032173d32 100644 --- a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp @@ -64,7 +64,7 @@ bool PhraseReplacementMap::open(const char *path) while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) { keyValueMap[keyValue.key] = keyValue.value; } - + // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) if (state == KeyValueBlobReader::State::ERROR) { close(); return false; -- Gitee From e8f895a642134208e98d1fd4f4344f52d39a44a0 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 19 Jan 2022 19:49:14 +0800 Subject: [PATCH 083/163] Shiki: NT351 Emu // Add Keyboard Shortcut + l10n tweaks. --- Source/Base.lproj/preferences.xib | 2 +- Source/InputMethodController.mm | 16 ++++++++++++---- Source/PreferencesModule.swift | 6 ++++++ Source/en.lproj/Localizable.strings | 1 + Source/en.lproj/preferences.strings | 4 ++-- Source/ja.lproj/Localizable.strings | 1 + Source/ja.lproj/preferences.strings | 4 ++-- Source/zh-Hans.lproj/Localizable.strings | 1 + Source/zh-Hans.lproj/preferences.strings | 2 +- Source/zh-Hant.lproj/Localizable.strings | 1 + Source/zh-Hant.lproj/preferences.strings | 2 +- 11 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index 2075d5a53..fdc2facc8 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -240,7 +240,7 @@ @@ -156,6 +140,22 @@ DQ + @@ -172,11 +172,12 @@ DQ - + + @@ -189,7 +190,6 @@ DQ - diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index c72236dda..314038546 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -12,7 +12,6 @@ #import "Gramambular.h" #import "vChewingLM.h" #import "UserOverrideModel.h" -#import "frmAboutWindow.h" @interface vChewingInputMethodController : IMKInputController { diff --git a/Source/frmAboutWindow.h b/Source/frmAboutWindow.h deleted file mode 100644 index a8e22fc83..000000000 --- a/Source/frmAboutWindow.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * frmAboutWindow.h - * - * Copyright 2021-2022 vChewing Project (3-Clause BSD License). - * Derived from 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#import - -@interface frmAboutWindow : NSWindowController - -+ (instancetype) defaultController; -- (void) showWithSender:(id)sender; - -@property (nonatomic) IBOutlet NSTextField *appNameLabel; -@property (nonatomic) IBOutlet NSTextField *appVersionLabel; -@property (nonatomic) IBOutlet NSTextField *appCopyrightLabel; -@property (nonatomic) IBOutlet NSTextView *appEULAContent; - -@end diff --git a/Source/frmAboutWindow.m b/Source/frmAboutWindow.m deleted file mode 100644 index 69f46732a..000000000 --- a/Source/frmAboutWindow.m +++ /dev/null @@ -1,67 +0,0 @@ -/* - * frmAboutWindow.m - * - * Copyright 2021-2022 vChewing Project (3-Clause BSD License). - * Derived from 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#import "frmAboutWindow.h" - - -@implementation frmAboutWindow -@synthesize appNameLabel; -@synthesize appVersionLabel; -@synthesize appCopyrightLabel; -@synthesize appEULAContent; - -+ (instancetype) defaultController { - - static id staticInstance = nil; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - staticInstance = [[self alloc] init]; - }); - - return staticInstance; -} - - -#pragma mark - Initialization - - -- (instancetype) init { - return [super initWithWindowNibName:@"frmAboutWindow" owner:self]; -} - - -#pragma mark - NSWindowController - - -- (void) windowDidLoad { - - [super windowDidLoad]; - [self.window standardWindowButton:NSWindowCloseButton].hidden = true; - [self.window standardWindowButton:NSWindowMiniaturizeButton].hidden = true; - [self.window standardWindowButton:NSWindowZoomButton].hidden = true; - [self updateInfo]; -} - -- (void) updateInfo { - - NSString *installingVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey]; - NSString *versionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; - - NSDictionary* localizedInfoDictionary = [[NSBundle mainBundle] localizedInfoDictionary]; - - self.appNameLabel.stringValue = [localizedInfoDictionary objectForKey:@"CFBundleName"]; - self.appVersionLabel.stringValue = [NSString stringWithFormat:@"%@ Build %@", versionString, installingVersion]; - self.appCopyrightLabel.stringValue = [localizedInfoDictionary objectForKey:@"NSHumanReadableCopyright"]; - self.appEULAContent.string = [localizedInfoDictionary objectForKey:@"CFEULAContent"]; -} - -- (void) showWithSender:(id)sender { -} - -@end diff --git a/Source/frmAboutWindow.swift b/Source/frmAboutWindow.swift new file mode 100644 index 000000000..102c0178f --- /dev/null +++ b/Source/frmAboutWindow.swift @@ -0,0 +1,35 @@ +/* + * frmAboutWindow.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +import Cocoa + +@objc(frmAboutWindow) class frmAboutWindow: NSWindowController { + @IBOutlet weak var appVersionLabel: NSTextField! + @IBOutlet weak var appCopyrightLabel: NSTextField! + @IBOutlet var appEULAContent: NSTextView! + + override func windowDidLoad() { + super.windowDidLoad() + + window?.standardWindowButton(.closeButton)?.isHidden = true + window?.standardWindowButton(.miniaturizeButton)?.isHidden = true + window?.standardWindowButton(.zoomButton)?.isHidden = true + guard let installingVersion = Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String, + let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { + return + } + if let copyrightLabel = Bundle.main.localizedInfoDictionary?["NSHumanReadableCopyright"] as? String { + appCopyrightLabel.stringValue = copyrightLabel + } + if let eulaContent = Bundle.main.localizedInfoDictionary?["CFEULAContent"] as? String { + appEULAContent.string = eulaContent + } + appVersionLabel.stringValue = String(format: "%@ Build %@", versionString, installingVersion) + } + +} diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index 1e362a3bf..7fc9cd38d 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -10,7 +10,6 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // -#import "frmAboutWindow.h" #import // @import Foundation; @interface LanguageModelManager : NSObject + (void)loadDataModels; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 40c774901..6317409b7 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 5B21711B279B998C00F91A2B /* Chronosphere.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B217119279B998800F91A2B /* Chronosphere.m */; }; 5B21711E279B9AD900F91A2B /* ArchiveUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21711C279B9AD700F91A2B /* ArchiveUtil.swift */; }; 5B217126279BA37500F91A2B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B217124279BA37300F91A2B /* AppDelegate.swift */; }; + 5B217128279BB22700F91A2B /* frmAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B217127279BB22700F91A2B /* frmAboutWindow.swift */; }; 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */; }; 5B58E87F278413E7003EA2AD /* BSDLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* BSDLicense.txt */; }; 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */; }; @@ -39,7 +40,6 @@ 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D052791DA6700838ADB /* AppDelegate.swift */; }; 5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE798A32792E58A00337FF9 /* TooltipController.swift */; }; 5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE798A62793280C00337FF9 /* NotifierController.swift */; }; - 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; @@ -95,6 +95,7 @@ 5B217119279B998800F91A2B /* Chronosphere.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Chronosphere.m; sourceTree = ""; }; 5B21711C279B9AD700F91A2B /* ArchiveUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveUtil.swift; sourceTree = ""; }; 5B217124279BA37300F91A2B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5B217127279BB22700F91A2B /* frmAboutWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = frmAboutWindow.swift; sourceTree = ""; }; 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = ""; }; 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; 5B42B64127877D6500BB9B9F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/preferences.strings"; sourceTree = ""; }; @@ -143,8 +144,6 @@ 5BDF2D052791DA6700838ADB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5BE798A32792E58A00337FF9 /* TooltipController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = ""; }; 5BE798A62793280C00337FF9 /* NotifierController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotifierController.swift; sourceTree = ""; }; - 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; - 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; 5BF4A70327844DD0007DC6E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; 5BF4A70527844DD2007DC6E7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmAboutWindow.strings; sourceTree = ""; }; 5BF4A70727844DD3007DC6E7 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = ""; }; @@ -362,8 +361,6 @@ 6A0D4F1215FC0EB100ABF4B3 /* Engine */, 6ACA41E715FC1D9000935EF6 /* Installer */, 6A0D4F4715FC0EB900ABF4B3 /* Resources */, - 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */, - 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */, 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */, 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, 5B5F4F91279294A300922DC2 /* LanguageModelManager.h */, @@ -371,6 +368,7 @@ 6A0D4EC815FC0D6400ABF4B3 /* main.swift */, 6A0D4EF615FC0DA600ABF4B3 /* vChewing-Prefix.pch */, 5BDF2D052791DA6700838ADB /* AppDelegate.swift */, + 5B217127279BB22700F91A2B /* frmAboutWindow.swift */, 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */, 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */, D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */, @@ -704,7 +702,6 @@ 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.swift in Sources */, - 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */, 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */, 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */, @@ -719,6 +716,7 @@ 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */, 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */, + 5B217128279BB22700F91A2B /* frmAboutWindow.swift in Sources */, 5BD13F482794F0A6000E429F /* PhraseReplacementMap.cpp in Sources */, 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, -- Gitee From ca5b36bb0fda718b81393fe6fb6e25db83298d27 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 22 Jan 2022 13:33:03 +0800 Subject: [PATCH 097/163] Shiki: Deprecate "main.m" in the Swiftified Installer. --- Source/Installer/AppDelegate.swift | 1 + Source/Installer/main.m | 14 -------------- vChewing.xcodeproj/project.pbxproj | 4 ---- 3 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 Source/Installer/main.m diff --git a/Source/Installer/AppDelegate.swift b/Source/Installer/AppDelegate.swift index 32eb616ba..21d0268b3 100644 --- a/Source/Installer/AppDelegate.swift +++ b/Source/Installer/AppDelegate.swift @@ -18,6 +18,7 @@ private let kTargetFullBinPartialPath = "~/Library/Input Methods/vChewing.app/Co private let kTranslocationRemovalTickInterval: TimeInterval = 0.5 private let kTranslocationRemovalDeadline: TimeInterval = 60.0 +@main @objc (AppDelegate) class AppDelegate: NSWindowController, NSApplicationDelegate { @IBOutlet weak private var installButton: NSButton! diff --git a/Source/Installer/main.m b/Source/Installer/main.m deleted file mode 100644 index 13f89c58c..000000000 --- a/Source/Installer/main.m +++ /dev/null @@ -1,14 +0,0 @@ -/* - * main.m - * - * Copyright 2021-2022 vChewing Project (3-Clause BSD License). - * Derived from 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#import - -int main(int argc, char *argv[]) -{ - return NSApplicationMain(argc, (const char **)argv); -} diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 6317409b7..b2e62e065 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -61,7 +61,6 @@ 6ACA41FA15FC1D9000935EF6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EA15FC1D9000935EF6 /* InfoPlist.strings */; }; 6ACA41FC15FC1D9000935EF6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41EE15FC1D9000935EF6 /* Localizable.strings */; }; 6ACA41FD15FC1D9000935EF6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */; }; - 6ACA41FF15FC1D9000935EF6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACA41F415FC1D9000935EF6 /* main.m */; }; 6ACA420215FC1E5200935EF6 /* vChewing.app in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; }; 6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */; }; D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */; }; @@ -223,7 +222,6 @@ 6ACA41EF15FC1D9000935EF6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Installer-Info.plist"; path = "Source/Installer/Installer-Info.plist"; sourceTree = SOURCE_ROOT; }; 6ACA41F315FC1D9000935EF6 /* Installer-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Installer-Prefix.pch"; path = "Source/Installer/Installer-Prefix.pch"; sourceTree = SOURCE_ROOT; }; - 6ACA41F415FC1D9000935EF6 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Source/Installer/main.m; sourceTree = SOURCE_ROOT; }; 6ACA41F515FC1D9000935EF6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; 6ACA41F715FC1D9000935EF6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonModalAlertWindowController.xib; sourceTree = ""; }; @@ -522,7 +520,6 @@ 6ACA41F015FC1D9000935EF6 /* MainMenu.xib */, 6ACA41F215FC1D9000935EF6 /* Installer-Info.plist */, 6ACA41F315FC1D9000935EF6 /* Installer-Prefix.pch */, - 6ACA41F415FC1D9000935EF6 /* main.m */, ); path = Installer; sourceTree = ""; @@ -730,7 +727,6 @@ files = ( 5B21711E279B9AD900F91A2B /* ArchiveUtil.swift in Sources */, 5B21711B279B998C00F91A2B /* Chronosphere.m in Sources */, - 6ACA41FF15FC1D9000935EF6 /* main.m in Sources */, 5B217126279BA37500F91A2B /* AppDelegate.swift in Sources */, 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */, ); -- Gitee From 4f518c6c4fc8fbe487c8cb9fb9c1ec5bf4dacc13 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 22 Jan 2022 13:33:32 +0800 Subject: [PATCH 098/163] Tweaking Makefile to cope with the Swiftified installer. --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index 319c64419..8e82984e7 100644 --- a/Makefile +++ b/Makefile @@ -26,11 +26,9 @@ permission-check: [ -w "$(DSTROOT)" ] && [ -w "$(VC_APP_ROOT)" ] || sudo chown -R ${USER} "$(DSTROOT)" install-debug: permission-check - rm -rf "$(VC_APP_ROOT)" open Build/Products/Debug/vChewingInstaller.app install-release: permission-check - rm -rf "$(VC_APP_ROOT)" open Build/Products/Release/vChewingInstaller.app .PHONY: clean -- Gitee From 56f5c52ce2cdd03a97ab24a880af790c5b37fa25 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 22 Jan 2022 16:52:04 +0800 Subject: [PATCH 099/163] Add a function to allow users to reboot the IME. - By terminating the IME, the IME gets restarted immediately. --- Source/InputMethodController.mm | 9 +++++++++ Source/en.lproj/Localizable.strings | 1 + Source/ja.lproj/Localizable.strings | 1 + Source/zh-Hans.lproj/Localizable.strings | 1 + Source/zh-Hant.lproj/Localizable.strings | 1 + 5 files changed, 13 insertions(+) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index ca17a4dc8..3f2d96b50 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -167,6 +167,9 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItemWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; [menu addItemWithTitle:NSLocalizedString(@"Check for Updates…", @"") action:@selector(checkForUpdate:) keyEquivalent:@""]; [menu addItemWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""]; + if (optionKeyPressed) { + [menu addItemWithTitle:NSLocalizedString(@"Reboot vChewing…", @"") action:@selector(selfTerminate:) keyEquivalent:@""]; + } return menu; } @@ -1525,6 +1528,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } } } +- (void)selfTerminate:(id)sender +{ + NSLog(@"vChewing App self-terminated on request."); + [NSApplication.sharedApplication terminate:nil]; +} + - (void)checkForUpdate:(id)sender { [(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES]; diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index ce7224abc..1a03d012c 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -35,3 +35,4 @@ "Maximum 15 candidate keys allowed." = "Maximum 15 candidate keys allowed."; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ Phrase replacement mode enabled, interfering user phrase entry."; "NT351 BPMF EMU" = "NT351 Per-Char Select Mode"; +"Reboot vChewing…" = "Reboot vChewing…"; diff --git a/Source/ja.lproj/Localizable.strings b/Source/ja.lproj/Localizable.strings index ae4f5088c..b38918e68 100644 --- a/Source/ja.lproj/Localizable.strings +++ b/Source/ja.lproj/Localizable.strings @@ -35,3 +35,4 @@ "Maximum 15 candidate keys allowed." = "言選り用キー陣列には最多15つキー登録できます。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 言葉置換機能稼働中、新添付言葉にも影響。"; "NT351 BPMF EMU" = "全候補入力モード"; +"Reboot vChewing…" = "入力アプリ再起動…"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index aaa0bbf74..c28a350c5 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -35,3 +35,4 @@ "Maximum 15 candidate keys allowed." = "选字键最多只能指定十五个。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 语汇置换功能已启用,会波及语汇自订。"; "NT351 BPMF EMU" = "模拟逐字选字输入"; +"Reboot vChewing…" = "重新启动输入法…"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index a551893c0..c76332c12 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -35,3 +35,4 @@ "Maximum 15 candidate keys allowed." = "選字鍵最多只能指定十五個。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 語彙置換功能已啟用,會波及語彙自訂。"; "NT351 BPMF EMU" = "模擬逐字選字輸入"; +"Reboot vChewing…" = "重新啟動輸入法…"; -- Gitee From 027fdfd57595475158705029cd12584983f71eeb Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 22 Jan 2022 19:20:35 +0800 Subject: [PATCH 100/163] Remove Useless CMakeLists --- Source/Engine/vChewing/CMakeLists.txt | 24 ------------------------ vChewing.xcodeproj/project.pbxproj | 4 ---- 2 files changed, 28 deletions(-) delete mode 100644 Source/Engine/vChewing/CMakeLists.txt diff --git a/Source/Engine/vChewing/CMakeLists.txt b/Source/Engine/vChewing/CMakeLists.txt deleted file mode 100644 index 7a97530f6..000000000 --- a/Source/Engine/vChewing/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -cmake_minimum_required(VERSION 3.17) -project(KeyValueBlobReader) - -set(CMAKE_CXX_STANDARD 17) - -add_library(KeyValueBlobReader KeyValueBlobReader.cpp KeyValueBlobReader.h) - -# Let CMake fetch Google Test for us. -# https://github.com/google/googletest/tree/main/googletest#incorporating-into-an-existing-cmake-project -include(FetchContent) - -FetchContent_Declare( - googletest - # Specify the commit you depend on and update it regularly. - URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip -) -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -# Test target declarations. -add_executable(KeyValueBlobReadTest KeyValueBlobReaderTest.cpp) -target_link_libraries(KeyValueBlobReadTest gtest_main KeyValueBlobReader) -add_test(NAME KeyValueBlobReadTest COMMAND KeyValueBlobReadTest) diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index b2e62e065..06414c928 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */; }; 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */; }; 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */; }; - 5BC2D2872793B434002C0BEC /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2D2852793B434002C0BEC /* CMakeLists.txt */; }; 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; }; 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */; }; @@ -116,7 +115,6 @@ 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewingInstaller-Bridging-Header.h"; sourceTree = ""; }; 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyValueBlobReader.h; sourceTree = ""; }; - 5BC2D2852793B434002C0BEC /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeyValueBlobReader.cpp; sourceTree = ""; }; 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmacsKeyHelper.swift; sourceTree = ""; }; 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesModule.swift; sourceTree = ""; }; @@ -272,7 +270,6 @@ isa = PBXGroup; children = ( 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */, - 5BC2D2852793B434002C0BEC /* CMakeLists.txt */, 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */, 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */, ); @@ -647,7 +644,6 @@ 5BD0D19F279454F60008F48E /* Beep.aif in Resources */, 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */, 5B000FC4278495AD004F02AC /* SimpBopomofo@2x.tiff in Resources */, - 5BC2D2872793B434002C0BEC /* CMakeLists.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; -- Gitee From 86650698a3b5bb03ff22807d24ea263d87c50078 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 23 Jan 2022 16:41:02 +0800 Subject: [PATCH 101/163] LangModelMgr Typo Fix. --- Source/LanguageModelManager.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 139ded5f6..6ba4b0db6 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -126,7 +126,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return NO; } - BOOL shuoldAddLineBreakAtFront = NO; + BOOL shouldAddLineBreakAtFront = NO; NSString *path = [self userPhrasesDataPath:inputMode]; if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { @@ -140,7 +140,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing NSData *data = [readFile readDataToEndOfFile]; const void *bytes = [data bytes]; if (*(char *)bytes != '\n') { - shuoldAddLineBreakAtFront = YES; + shouldAddLineBreakAtFront = YES; } [readFile closeFile]; } @@ -148,7 +148,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing } NSMutableString *currentMarkedPhrase = [NSMutableString string]; - if (shuoldAddLineBreakAtFront) { + if (shouldAddLineBreakAtFront) { [currentMarkedPhrase appendString:@"\n"]; } [currentMarkedPhrase appendString:userPhrase]; -- Gitee From ce71245c080644789cf8441c1f21ccf2d37032df Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 23 Jan 2022 16:42:23 +0800 Subject: [PATCH 102/163] Ensure the EOF \n in both UserPhrases and Replacement Map. --- Source/Engine/LanguageModel/FastLM.cpp | 2 +- .../LanguageModel/PhraseReplacementMap.cpp | 17 +++++++++++++++++ Source/Engine/LanguageModel/UserPhrasesLM.cpp | 18 +++++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Source/Engine/LanguageModel/FastLM.cpp b/Source/Engine/LanguageModel/FastLM.cpp index bcfcf1940..66b9418b2 100644 --- a/Source/Engine/LanguageModel/FastLM.cpp +++ b/Source/Engine/LanguageModel/FastLM.cpp @@ -41,7 +41,7 @@ bool FastLM::open(const char *path) char z; zfd.get(z); if(z!='\n'){ - syslog(LOG_CONS, "REPORT: File is not ended with a new line.\n"); + syslog(LOG_CONS, "REPORT: Core Language Data file is not ended with a new line.\n"); syslog(LOG_CONS, "PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); ofstream zfdo(path, std::ios_base::app); zfdo << std::endl; diff --git a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp index 032173d32..0374100e8 100644 --- a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.cpp @@ -6,6 +6,7 @@ * Some rights reserved. See "LICENSE.TXT" for details. */ +#include #include #include #include @@ -38,6 +39,22 @@ bool PhraseReplacementMap::open(const char *path) return false; } + std::fstream zfd(path); + zfd.seekg(-1,std::ios_base::end); + char z; + zfd.get(z); + if(z!='\n'){ + syslog(LOG_CONS, "REPORT: Phrase Replacement Map File is not ended with a new line.\n"); + syslog(LOG_CONS, "PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); + std::ofstream zfdo(path, std::ios_base::app); + zfdo << std::endl; + zfdo.close(); + if (zfdo.fail()) { + syslog(LOG_CONS, "REPORT: Failed to append a newline to the data file. Insufficient Privileges?\n"); + return false; + } + } + fd = ::open(path, O_RDONLY); if (fd == -1) { printf("open:: file not exist"); diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.cpp b/Source/Engine/LanguageModel/UserPhrasesLM.cpp index 583e9a4ab..733a371d2 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.cpp +++ b/Source/Engine/LanguageModel/UserPhrasesLM.cpp @@ -7,12 +7,12 @@ */ #include "UserPhrasesLM.h" - #include #include #include #include #include +#include #include "KeyValueBlobReader.h" @@ -38,6 +38,22 @@ bool UserPhrasesLM::open(const char *path) return false; } + std::fstream zfd(path); + zfd.seekg(-1,std::ios_base::end); + char z; + zfd.get(z); + if(z!='\n'){ + syslog(LOG_CONS, "REPORT: User Phrase Data File is not ended with a new line.\n"); + syslog(LOG_CONS, "PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); + std::ofstream zfdo(path, std::ios_base::app); + zfdo << std::endl; + zfdo.close(); + if (zfdo.fail()) { + syslog(LOG_CONS, "REPORT: Failed to append a newline to the data file. Insufficient Privileges?\n"); + return false; + } + } + fd = ::open(path, O_RDONLY); if (fd == -1) { printf("open:: file not exist"); -- Gitee From a713cb324ccd4f2bd8eb962cd28d11eb3b739828 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 23 Jan 2022 18:21:49 +0800 Subject: [PATCH 103/163] Zonble: Implementing AllUnigrams to autosort candidates. - This brings a little side effect that the user candidate won't reflect duplicated user phrases anymore. However, it is meaningless to feel entangled with that since we can later introduce a file-content deduplicator for user phrase data file. - Though this is not a hard requirement, the CNS11643 support need this module to boost the efficiency of the dev process. --- Source/Engine/LanguageModel/vChewingLM.cpp | 101 ++++++++------------- Source/Engine/LanguageModel/vChewingLM.h | 20 ++-- 2 files changed, 52 insertions(+), 69 deletions(-) diff --git a/Source/Engine/LanguageModel/vChewingLM.cpp b/Source/Engine/LanguageModel/vChewingLM.cpp index 15bf43a6c..8fd5b82af 100644 --- a/Source/Engine/LanguageModel/vChewingLM.cpp +++ b/Source/Engine/LanguageModel/vChewingLM.cpp @@ -9,7 +9,6 @@ #include "vChewingLM.h" #include #include -#include using namespace vChewing; @@ -46,7 +45,8 @@ void vChewingLM::loadUserPhrases(const char* userPhrasesDataPath, } } -void vChewingLM::loadPhraseReplacementMap(const char* phraseReplacementPath) { +void vChewingLM::loadPhraseReplacementMap(const char* phraseReplacementPath) +{ if (phraseReplacementPath) { m_phraseReplacement.close(); m_phraseReplacement.open(phraseReplacementPath); @@ -60,90 +60,69 @@ const vector vChewingLM::bigramsForKeys(const string& preceedingKey, con const vector vChewingLM::unigramsForKey(const string& key) { - vector unigrams; + vector allUnigrams; vector userUnigrams; - - // Use unordered_set so that you don't have to do O(n*m) + unordered_set excludedValues; - unordered_set userValues; - + unordered_set insertedValues; + if (m_excludedPhrases.hasUnigramsForKey(key)) { vector excludedUnigrams = m_excludedPhrases.unigramsForKey(key); transform(excludedUnigrams.begin(), excludedUnigrams.end(), inserter(excludedValues, excludedValues.end()), - [](const Unigram &u) { return u.keyValue.value; }); + [](const Unigram& u) { return u.keyValue.value; }); } - + if (m_userPhrases.hasUnigramsForKey(key)) { vector rawUserUnigrams = m_userPhrases.unigramsForKey(key); - vector filterredUserUnigrams; - - for (auto&& unigram : rawUserUnigrams) { - if (excludedValues.find(unigram.keyValue.value) == excludedValues.end()) { - filterredUserUnigrams.push_back(unigram); - } - } - - transform(filterredUserUnigrams.begin(), filterredUserUnigrams.end(), - inserter(userValues, userValues.end()), - [](const Unigram &u) { return u.keyValue.value; }); - - if (m_phraseReplacementEnabled) { - for (auto&& unigram : filterredUserUnigrams) { - string value = unigram.keyValue.value; - string replacement = m_phraseReplacement.valueForKey(value); - if (replacement != "") { - unigram.keyValue.value = replacement; - } - unigrams.push_back(unigram); - } - } else { - unigrams = filterredUserUnigrams; - } + userUnigrams = filterAndTransformUnigrams(rawUserUnigrams, excludedValues, insertedValues); } if (m_languageModel.hasUnigramsForKey(key)) { - vector globalUnigrams = m_languageModel.unigramsForKey(key); - - for (auto&& unigram : globalUnigrams) { - string value = unigram.keyValue.value; - if (excludedValues.find(value) == excludedValues.end() && - userValues.find(value) == userValues.end()) { - if (m_phraseReplacementEnabled) { - string replacement = m_phraseReplacement.valueForKey(value); - if (replacement != "") { - unigram.keyValue.value = replacement; - } - } - unigrams.push_back(unigram); - } - } + vector rawGlobalUnigrams = m_languageModel.unigramsForKey(key); + allUnigrams = filterAndTransformUnigrams(rawGlobalUnigrams, excludedValues, insertedValues); } - - unigrams.insert(unigrams.begin(), userUnigrams.begin(), userUnigrams.end()); - return unigrams; + + allUnigrams.insert(allUnigrams.begin(), userUnigrams.begin(), userUnigrams.end()); + return allUnigrams; } bool vChewingLM::hasUnigramsForKey(const string& key) { - if (key == " ") { - return true; - } - if (!m_excludedPhrases.hasUnigramsForKey(key)) { - return m_userPhrases.hasUnigramsForKey(key) || - m_languageModel.hasUnigramsForKey(key); + return m_userPhrases.hasUnigramsForKey(key) || m_languageModel.hasUnigramsForKey(key); } - + return unigramsForKey(key).size() > 0; } - + void vChewingLM::setPhraseReplacementEnabled(bool enabled) { - m_phraseReplacementEnabled = enabled; + m_phraseReplacementEnabled = enabled; } - + bool vChewingLM::phraseReplacementEnabled() { return m_phraseReplacementEnabled; } + +const vector vChewingLM::filterAndTransformUnigrams(vector unigrams, const unordered_set& excludedValues, unordered_set& insertedValues) +{ + vector results; + + for (auto&& unigram : unigrams) { + string value = unigram.keyValue.value; + if (m_phraseReplacementEnabled) { + string replacement = m_phraseReplacement.valueForKey(value); + if (replacement != "") { + value = replacement; + unigram.keyValue.value = value; + } + } + if (excludedValues.find(value) == excludedValues.end() && insertedValues.find(value) == insertedValues.end()) { + results.push_back(unigram); + insertedValues.insert(value); + } + } + return results; +} diff --git a/Source/Engine/LanguageModel/vChewingLM.h b/Source/Engine/LanguageModel/vChewingLM.h index 95bf0d09e..ce339db5a 100644 --- a/Source/Engine/LanguageModel/vChewingLM.h +++ b/Source/Engine/LanguageModel/vChewingLM.h @@ -10,9 +10,10 @@ #define VCHEWINGLM_H #include -#include "FastLM.h" #include "UserPhrasesLM.h" +#include "FastLM.h" #include "PhraseReplacementMap.h" +#include namespace vChewing { @@ -22,20 +23,23 @@ class vChewingLM : public LanguageModel { public: vChewingLM(); ~vChewingLM(); - - void loadLanguageModel(const char* languageModelDataPath); - void loadUserPhrases(const char* userPhrasesDataPath, - const char* excludedPhrasesDataPath); + + void loadLanguageModel(const char* languageModelPath); + void loadUserPhrases(const char* userPhrasesPath, const char* excludedPhrasesPath); void loadPhraseReplacementMap(const char* phraseReplacementPath); - + const vector bigramsForKeys(const string& preceedingKey, const string& key); const vector unigramsForKey(const string& key); bool hasUnigramsForKey(const string& key); - + void setPhraseReplacementEnabled(bool enabled); bool phraseReplacementEnabled(); - + protected: + const vector filterAndTransformUnigrams(vector unigrams, + const std::unordered_set& excludedValues, + std::unordered_set& insertedValues); + FastLM m_languageModel; UserPhrasesLM m_userPhrases; UserPhrasesLM m_excludedPhrases; -- Gitee From 8b9e5ca57ec7e0fe3f8337bff2c9b787be4dd362 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 23 Jan 2022 23:15:29 +0800 Subject: [PATCH 104/163] Lukhnos: Optimized KeyValueBlobReader. --- Source/Engine/vChewing/KeyValueBlobReader.cpp | 180 +++++++++--------- Source/Engine/vChewing/KeyValueBlobReader.h | 80 ++++---- 2 files changed, 137 insertions(+), 123 deletions(-) diff --git a/Source/Engine/vChewing/KeyValueBlobReader.cpp b/Source/Engine/vChewing/KeyValueBlobReader.cpp index 2e3ac1524..57e3bd541 100644 --- a/Source/Engine/vChewing/KeyValueBlobReader.cpp +++ b/Source/Engine/vChewing/KeyValueBlobReader.cpp @@ -10,116 +10,118 @@ namespace vChewing { -KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue* out) { - static auto new_line = [](char c) { return c == '\n' || c == '\r'; }; - static auto blank = [](char c) { return c == ' ' || c == '\t'; }; - static auto blank_or_newline = [](char c) { return blank(c) || new_line(c); }; - static auto content_char = [](char c) { - return !blank(c) && !new_line(c); - }; - - if (state_ == State::ERROR) { - return state_; - } +KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue* out) +{ + static auto new_line = [](char c) { return c == '\n' || c == '\r'; }; + static auto blank = [](char c) { return c == ' ' || c == '\t'; }; + static auto blank_or_newline + = [](char c) { return blank(c) || new_line(c); }; + static auto content_char = [](char c) { return !blank(c) && !new_line(c); }; + + if (state_ == State::ERROR) { + return state_; + } + + const char* key_begin = nullptr; + size_t key_length = 0; + const char* value_begin = nullptr; + size_t value_length = 0; + + while (true) { + state_ = SkipUntilNot(blank_or_newline); + if (state_ != State::CAN_CONTINUE) { + return state_; + } + + // Check if it's a comment line; if so, read until end of line. + if (*current_ != '#') { + break; + } + state_ = SkipUntil(new_line); + if (state_ != State::CAN_CONTINUE) { + return state_; + } + } - const char* key_begin = nullptr; - size_t key_length = 0; - const char* value_begin = nullptr; - size_t value_length = 0; + // No need to check whether* current_ is a content_char, since content_char + // is defined as not blank and not new_line. - while (true) { - state_ = SkipUntilNot(blank_or_newline); + key_begin = current_; + state_ = SkipUntilNot(content_char); if (state_ != State::CAN_CONTINUE) { - return state_; + goto error; } + key_length = current_ - key_begin; - // Check if it's a comment line; if so, read until end of line. - if (*current_ != '#') { - break; + // There should be at least one blank character after the key string. + if (!blank(*current_)) { + goto error; } - state_ = SkipUntil(new_line); + + state_ = SkipUntilNot(blank); if (state_ != State::CAN_CONTINUE) { - return state_; + goto error; } - } - - // No need to check whether* current_ is a content_char, since content_char - // is defined as not blank and not new_line. - - key_begin = current_; - state_ = SkipUntilNot(content_char); - if (state_ != State::CAN_CONTINUE) { - goto error; - } - key_length = current_ - key_begin; - - // There should be at least one blank character after the key string. - if (!blank(*current_)) { - goto error; - } - - state_ = SkipUntilNot(blank); - if (state_ != State::CAN_CONTINUE) { - goto error; - } - - if (!content_char(*current_)) { - goto error; - } - - value_begin = current_; - // value must only contain content characters, blanks not are allowed. - // also, there's no need to check the state after this, since we will always - // emit the value. This also avoids the situation where trailing spaces in a - // line would become part of the value. - SkipUntilNot(content_char); - value_length = current_ - value_begin; - - // Unconditionally skip until the end of the line. This prevents the case - // like "foo bar baz\n" where baz should not be treated as the Next key. - SkipUntil(new_line); - - if (out != nullptr) { - *out = KeyValue{ - std::string_view{key_begin, key_length}, - std::string_view{value_begin, value_length}}; - } - state_ = State::HAS_PAIR; - return state_; + + if (!content_char(*current_)) { + goto error; + } + + value_begin = current_; + // value must only contain content characters, blanks not are allowed. + // also, there's no need to check the state after this, since we will always + // emit the value. This also avoids the situation where trailing spaces in a + // line would become part of the value. + SkipUntilNot(content_char); + value_length = current_ - value_begin; + + // Unconditionally skip until the end of the line. This prevents the case + // like "foo bar baz\n" where baz should not be treated as the Next key. + SkipUntil(new_line); + + if (out != nullptr) { + *out = KeyValue { std::string_view { key_begin, key_length }, + std::string_view { value_begin, value_length } }; + } + state_ = State::HAS_PAIR; + return state_; error: - state_ = State::ERROR; - return State::ERROR; + state_ = State::ERROR; + return state_; } KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot( - const std::function& f) { - while (current_ != end_ &&* current_) { - if (!f(*current_)) { - return State::CAN_CONTINUE; + const std::function& f) +{ + while (current_ != end_ && *current_) { + if (!f(*current_)) { + return State::CAN_CONTINUE; + } + ++current_; } - ++current_; - } - return State::END; + return State::END; } KeyValueBlobReader::State KeyValueBlobReader::SkipUntil( - const std::function& f) { - while (current_ != end_ &&* current_) { - if (f(*current_)) { - return State::CAN_CONTINUE; + const std::function& f) +{ + while (current_ != end_ && *current_) { + if (f(*current_)) { + return State::CAN_CONTINUE; + } + ++current_; } - ++current_; - } - return State::END; + return State::END; } -std::ostream& operator<<(std::ostream& os, - const KeyValueBlobReader::KeyValue& kv) { - os << "(key: " << kv.key << ", value: " << kv.value << ")"; - return os; +std::ostream& operator<<( + std::ostream& os, const KeyValueBlobReader::KeyValue& kv) +{ + os << "(key: " << kv.key << ", value: " << kv.value << ")"; + return os; } -} // namespace vChewing +} // namespace vChewing diff --git a/Source/Engine/vChewing/KeyValueBlobReader.h b/Source/Engine/vChewing/KeyValueBlobReader.h index d95ef3738..2a07b9e83 100644 --- a/Source/Engine/vChewing/KeyValueBlobReader.h +++ b/Source/Engine/vChewing/KeyValueBlobReader.h @@ -31,49 +31,61 @@ namespace vChewing { class KeyValueBlobReader { - public: - enum class State : int { - // There are no more key-value pairs in this blob. - END = 0, - // The reader has produced a new key-value pair. - HAS_PAIR = 1, - // An error is encountered and the parsing stopped. - ERROR = -1, - // Internal-only state: the parser can continue parsing. - CAN_CONTINUE = 2 - }; +public: + enum class State : int { + // There are no more key-value pairs in this blob. + END = 0, + // The reader has produced a new key-value pair. + HAS_PAIR = 1, + // An error is encountered and the parsing stopped. + ERROR = -1, + // Internal-only state: the parser can continue parsing. + CAN_CONTINUE = 2 + }; - struct KeyValue { - constexpr KeyValue() : key(""), value("") {} - constexpr KeyValue(std::string_view k, std::string_view v) - : key(k), value(v) {} + struct KeyValue { + constexpr KeyValue() + : key("") + , value("") + { + } + constexpr KeyValue(std::string_view k, std::string_view v) + : key(k) + , value(v) + { + } - bool operator==(const KeyValue& another) const { - return key == another.key && value == another.value; - } + bool operator==(const KeyValue& another) const + { + return key == another.key && value == another.value; + } - std::string_view key; - std::string_view value; - }; + std::string_view key; + std::string_view value; + }; - KeyValueBlobReader(const char* blob, size_t size) - : current_(blob), end_(blob + size) {} + KeyValueBlobReader(const char* blob, size_t size) + : current_(blob) + , end_(blob + size) + { + } - // Parse the next key-value pair and return the state of the reader. If `out` - // is passed, out will be set to the produced key-value pair if there is one. - State Next(KeyValue* out = nullptr); + // Parse the next key-value pair and return the state of the reader. If + // `out` is passed, out will be set to the produced key-value pair if there + // is one. + State Next(KeyValue* out = nullptr); - private: - State SkipUntil(const std::function& f); - State SkipUntilNot(const std::function& f); +private: + State SkipUntil(const std::function& f); + State SkipUntilNot(const std::function& f); - const char* current_; - const char* end_; - State state_ = State::CAN_CONTINUE; + const char* current_; + const char* end_; + State state_ = State::CAN_CONTINUE; }; std::ostream& operator<<(std::ostream&, const KeyValueBlobReader::KeyValue&); -} // namespace vChewing +} // namespace vChewing -#endif // SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ +#endif // SOURCE_ENGINE_KEYVALUEBLOBREADER_H_ -- Gitee From e362151efe87d779b08f301bc31d2c19f2f9adf5 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 23 Jan 2022 17:28:52 +0800 Subject: [PATCH 105/163] Enable EscToCleanInputBuffer by default. --- Source/PreferencesModule.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 8fc21dbd6..e0d791c41 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -257,7 +257,7 @@ struct ComposingBufferSize { return halfWidthPunctuationEnabled } - @UserDefault(key: kEscToCleanInputBufferKey, defaultValue: false) + @UserDefault(key: kEscToCleanInputBufferKey, defaultValue: true) @objc static var escToCleanInputBuffer: Bool // MARK: Optional settings -- Gitee From 52140dcfde8b12c4c6905e64f8fc316a6e926407 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 24 Jan 2022 10:56:57 +0800 Subject: [PATCH 106/163] CNS // Phase 1: + Components & i18n Changes. --- Source/3rdParty/AWFileHash/AWFileHash.h | 23 + Source/3rdParty/AWFileHash/AWFileHash.m | 348 +++ Source/3rdParty/SSZipArchive/SSZipArchive.h | 45 + Source/3rdParty/SSZipArchive/SSZipArchive.m | 421 ++++ Source/3rdParty/SSZipArchive/minizip/crypt.h | 131 + Source/3rdParty/SSZipArchive/minizip/ioapi.h | 201 ++ Source/3rdParty/SSZipArchive/minizip/ioapi.m | 239 ++ .../3rdParty/SSZipArchive/minizip/mztools.h | 31 + .../3rdParty/SSZipArchive/minizip/mztools.m | 284 +++ Source/3rdParty/SSZipArchive/minizip/unzip.h | 437 ++++ Source/3rdParty/SSZipArchive/minizip/unzip.m | 2150 +++++++++++++++++ Source/3rdParty/SSZipArchive/minizip/zip.h | 362 +++ Source/3rdParty/SSZipArchive/minizip/zip.m | 2023 ++++++++++++++++ Source/Base.lproj/preferences.xib | 2 +- Source/InputMethodController.mm | 6 + Source/LanguageModelManager.h | 3 + Source/LanguageModelManager.mm | 15 +- Source/en.lproj/preferences.strings | 4 +- Source/ja.lproj/preferences.strings | 4 +- Source/vChewing-Bridging-Header.h | 1 + Source/zh-Hans.lproj/preferences.strings | 4 +- Source/zh-Hant.lproj/preferences.strings | 4 +- vChewing.xcodeproj/project.pbxproj | 78 + 23 files changed, 6806 insertions(+), 10 deletions(-) create mode 100644 Source/3rdParty/AWFileHash/AWFileHash.h create mode 100644 Source/3rdParty/AWFileHash/AWFileHash.m create mode 100755 Source/3rdParty/SSZipArchive/SSZipArchive.h create mode 100755 Source/3rdParty/SSZipArchive/SSZipArchive.m create mode 100755 Source/3rdParty/SSZipArchive/minizip/crypt.h create mode 100755 Source/3rdParty/SSZipArchive/minizip/ioapi.h create mode 100755 Source/3rdParty/SSZipArchive/minizip/ioapi.m create mode 100755 Source/3rdParty/SSZipArchive/minizip/mztools.h create mode 100755 Source/3rdParty/SSZipArchive/minizip/mztools.m create mode 100755 Source/3rdParty/SSZipArchive/minizip/unzip.h create mode 100755 Source/3rdParty/SSZipArchive/minizip/unzip.m create mode 100755 Source/3rdParty/SSZipArchive/minizip/zip.h create mode 100755 Source/3rdParty/SSZipArchive/minizip/zip.m diff --git a/Source/3rdParty/AWFileHash/AWFileHash.h b/Source/3rdParty/AWFileHash/AWFileHash.h new file mode 100644 index 000000000..029c8e359 --- /dev/null +++ b/Source/3rdParty/AWFileHash/AWFileHash.h @@ -0,0 +1,23 @@ +// +// AWFileHash.h +// Pods +// +// Created by Alexander Widerberg on 2015-02-17. +// +// + +#import + +@interface AWFileHash : NSObject + ++ (NSString *)md5HashOfData:(NSData *)data; ++ (NSString *)sha1HashOfData:(NSData *)data; ++ (NSString *)sha512HashOfData:(NSData *)data; ++ (NSString *)crc32HashOfData:(NSData *)data; + ++ (NSString *)md5HashOfFileAtPath:(NSString *)filePath; ++ (NSString *)sha1HashOfFileAtPath:(NSString *)filePath; ++ (NSString *)sha512HashOfFileAtPath:(NSString *)filePath; ++ (NSString *)crc32HashOfFileAtPath:(NSString *)filePath; + +@end diff --git a/Source/3rdParty/AWFileHash/AWFileHash.m b/Source/3rdParty/AWFileHash/AWFileHash.m new file mode 100644 index 000000000..219cb1e66 --- /dev/null +++ b/Source/3rdParty/AWFileHash/AWFileHash.m @@ -0,0 +1,348 @@ +// +// AWFileHash.m +// Pods +// +// Created by Alexander Widerberg on 2015-02-17. +// +// + +// Header file +#import "AWFileHash.h" + +// System framework and libraries +#include +#include +#include +#include + + +// Constants +size_t FileHashDefaultChunkSizeForReadingData = 4096; // in bytes + +// Function pointer types for functions used in the computation +// of a cryptographic hash. +typedef int (*FileHashInitFunction) (uint8_t *hashObjectPointer[]); +typedef int (*FileHashUpdateFunction) (uint8_t *hashObjectPointer[], const void *data, CC_LONG len); +typedef int (*FileHashFinalFunction) (unsigned char *md, uint8_t *hashObjectPointer[]); + +// Structure used to describe a hash computation context. +typedef struct _FileHashComputationContext { + FileHashInitFunction initFunction; + FileHashUpdateFunction updateFunction; + FileHashFinalFunction finalFunction; + size_t digestLength; + uint8_t **hashObjectPointer; +} FileHashComputationContext; + +#define MAX_READ_STREAM_STATUS_POLLING_ATTEMPTS 10 + +#define FileHashComputationContextInitialize(context, hashAlgorithmName) \ +CC_##hashAlgorithmName##_CTX hashObjectFor##hashAlgorithmName; \ +context.initFunction = (FileHashInitFunction)&CC_##hashAlgorithmName##_Init; \ +context.updateFunction = (FileHashUpdateFunction)&CC_##hashAlgorithmName##_Update; \ +context.finalFunction = (FileHashFinalFunction)&CC_##hashAlgorithmName##_Final; \ +context.digestLength = CC_##hashAlgorithmName##_DIGEST_LENGTH; \ +context.hashObjectPointer = (uint8_t **)&hashObjectFor##hashAlgorithmName + +#pragma mark - CRC32b implementation + +typedef struct { + CC_LONG crc; +} CC_CRC32_CTX; + +static int CC_CRC32_Init(CC_CRC32_CTX *c); +static int CC_CRC32_Update(CC_CRC32_CTX *c, const uint8_t *data, CC_LONG len); +static int CC_CRC32_Final(unsigned char *md, CC_CRC32_CTX *c); + +#define CC_CRC32_DIGEST_LENGTH 4 + +static CC_LONG crc32_tbl[256] = +{ + 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL, 0x076DC419L, + 0x706AF48FL, 0xE963A535L, 0x9E6495A3L, 0x0EDB8832L, 0x79DCB8A4L, + 0xE0D5E91EL, 0x97D2D988L, 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, + 0x90BF1D91L, 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL, + 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L, 0x136C9856L, + 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL, 0x14015C4FL, 0x63066CD9L, + 0xFA0F3D63L, 0x8D080DF5L, 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, + 0xA2677172L, 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL, + 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L, 0x32D86CE3L, + 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L, 0x26D930ACL, 0x51DE003AL, + 0xC8D75180L, 0xBFD06116L, 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, + 0xB8BDA50FL, 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L, + 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL, 0x76DC4190L, + 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL, 0x71B18589L, 0x06B6B51FL, + 0x9FBFE4A5L, 0xE8B8D433L, 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, + 0xE10E9818L, 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L, + 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL, 0x6C0695EDL, + 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L, 0x65B0D9C6L, 0x12B7E950L, + 0x8BBEB8EAL, 0xFCB9887CL, 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, + 0xFBD44C65L, 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L, + 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL, 0x4369E96AL, + 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L, 0x44042D73L, 0x33031DE5L, + 0xAA0A4C5FL, 0xDD0D7CC9L, 0x5005713CL, 0x270241AAL, 0xBE0B1010L, + 0xC90C2086L, 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL, + 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L, 0x59B33D17L, + 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL, 0xEDB88320L, 0x9ABFB3B6L, + 0x03B6E20CL, 0x74B1D29AL, 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, + 0x73DC1683L, 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L, + 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L, 0xF00F9344L, + 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL, 0xF762575DL, 0x806567CBL, + 0x196C3671L, 0x6E6B06E7L, 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, + 0x67DD4ACCL, 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L, + 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L, 0xD1BB67F1L, + 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL, 0xD80D2BDAL, 0xAF0A1B4CL, + 0x36034AF6L, 0x41047A60L, 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, + 0x4669BE79L, 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L, + 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL, 0xC5BA3BBEL, + 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L, 0xC2D7FFA7L, 0xB5D0CF31L, + 0x2CD99E8BL, 0x5BDEAE1DL, 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, + 0x026D930AL, 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L, + 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L, 0x92D28E9BL, + 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L, 0x86D3D2D4L, 0xF1D4E242L, + 0x68DDB3F8L, 0x1FDA836EL, 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, + 0x18B74777L, 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL, + 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L, 0xA00AE278L, + 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L, 0xA7672661L, 0xD06016F7L, + 0x4969474DL, 0x3E6E77DBL, 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, + 0x37D83BF0L, 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L, + 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L, 0xBAD03605L, + 0xCDD70693L, 0x54DE5729L, 0x23D967BFL, 0xB3667A2EL, 0xC4614AB8L, + 0x5D681B02L, 0x2A6F2B94L, 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, + 0x2D02EF8DL +}; + +int CC_CRC32_Init(CC_CRC32_CTX *c) { + c->crc = 0xFFFFFFFFL; + return 0; +} + +int CC_CRC32_Update(CC_CRC32_CTX *c, const uint8_t *data, CC_LONG len) { + CC_LONG crc = c->crc; + + for(CC_LONG i=0; i>8) ^ crc32_tbl[(crc&0xFF) ^ *data++]; + } + + c->crc = crc; + + return 0; +} + +int CC_CRC32_Final(unsigned char *md, CC_CRC32_CTX *c) { + CC_LONG crc = c->crc; + + crc = crc ^ 0xFFFFFFFFL; + + md[0] = (crc & 0xff000000UL) >> 24; + md[1] = (crc & 0x00ff0000UL) >> 16; + md[2] = (crc & 0x0000ff00UL) >> 8; + md[3] = (crc & 0x000000ffUL) ; + + return 0; +} + +#pragma mark - Begin implementation + +@implementation AWFileHash + +#pragma mark - +#pragma mark private class helpers ++ (NSString *)hashOfFileAtPath:(NSString *)filePath withComputationContext:(FileHashComputationContext *)context { + NSString *result = nil; + CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)filePath, kCFURLPOSIXPathStyle, (Boolean)false); + CFReadStreamRef readStream = fileURL ? CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL) : NULL; + + BOOL didSucceed = readStream ? (BOOL)CFReadStreamOpen(readStream) : NO; + + // Race condition while running om multiple threads patched with big thanks to Aaron Morse + if (!didSucceed) { + if (readStream) CFRelease(readStream); + if (fileURL) CFRelease(fileURL); + return nil; + } + CFStreamStatus myStreamStatus; + NSUInteger attempts = 0; + do { + sleep(1); + myStreamStatus = CFReadStreamGetStatus(readStream); + if (attempts++ > MAX_READ_STREAM_STATUS_POLLING_ATTEMPTS) { + if (readStream) CFRelease(readStream); + if (fileURL) CFRelease(fileURL); + return nil; + } + } while ((myStreamStatus != kCFStreamStatusOpen) && (myStreamStatus != kCFStreamStatusError)); + + if (myStreamStatus == kCFStreamStatusError) { + if (readStream) CFRelease(readStream); + if (fileURL) CFRelease(fileURL); + return nil; + } + // End race condition check + + // Use default value for the chunk size for reading data. + const size_t chunkSizeForReadingData = FileHashDefaultChunkSizeForReadingData; + + // Initialize the hash object + (*context->initFunction)(context->hashObjectPointer); + + // Feed the data to the hash object. + BOOL hasMoreData = YES; + while (hasMoreData) { + uint8_t buffer[chunkSizeForReadingData]; + CFIndex readBytesCount = CFReadStreamRead(readStream, (UInt8 *)buffer, (CFIndex)sizeof(buffer)); + + if (readBytesCount == -1) { + break; + } else if (readBytesCount == 0) { + hasMoreData = NO; + } else { + (*context->updateFunction)(context->hashObjectPointer, (const void *)buffer, (CC_LONG)readBytesCount); + } + } + + // Compute the hash digest + unsigned char digest[context->digestLength]; + (*context->finalFunction)(digest, context->hashObjectPointer); + + // Close the read stream. + CFReadStreamClose(readStream); + + // Proceed if the read operation succeeded. + didSucceed = !hasMoreData; + if (didSucceed) { + char hash[2 * sizeof(digest) + 1]; + for (size_t i = 0; i < sizeof(digest); ++i) { + snprintf(hash + (2 * i), 3, "%02x", (int)(digest[i])); + } + result = [NSString stringWithUTF8String:hash]; + } + + if (readStream) CFRelease(readStream); + if (fileURL) CFRelease(fileURL); + return result; +} + ++ (NSString *)hashOfNSData:(NSData *)data withComputationContext:(FileHashComputationContext *)context { + + NSString *result = nil; + BOOL didSucceed = NO; + + // Use default value for the chunk size for reading data. + size_t chunkSizeForReadingData = 0; + if(FileHashDefaultChunkSizeForReadingData > data.length) { + chunkSizeForReadingData = data.length; + } else { + chunkSizeForReadingData = FileHashDefaultChunkSizeForReadingData; + } + + // Initialize the hash object + (*context->initFunction)(context->hashObjectPointer); + + // Feed the data to the hash object. + BOOL hasMoreData = YES; + CFIndex readBytesCount = 0; + CFIndex totalOffset = 0; + NSRange range; + while (hasMoreData) { + uint8_t buffer[chunkSizeForReadingData]; + + readBytesCount = sizeof(buffer); + // Make sure that we read the correct amount of data + if(totalOffset+readBytesCount > data.length && !(totalOffset == data.length)) { + readBytesCount = (data.length-totalOffset); + } else if(totalOffset == data.length) { + readBytesCount = 0; + } else if(totalOffset > data.length) { + // This should not happen at any time (added for precaution) + break; + } + + range = NSMakeRange(totalOffset, readBytesCount); + + @try { + [data getBytes:buffer range:range]; + } + @catch (NSException *exception) { + NSLog(@"%@", exception.debugDescription); + break; + } + + totalOffset = totalOffset + readBytesCount; + + if (readBytesCount == -1) { + } else if (readBytesCount == 0) { + hasMoreData = NO; + } else { + (*context->updateFunction)(context->hashObjectPointer, (const void *)buffer, (CC_LONG)readBytesCount); + } + } + + // Compute the hash digest + unsigned char digest[context->digestLength]; + (*context->finalFunction)(digest, context->hashObjectPointer); + + // Proceed if the read operation succeeded. + didSucceed = !hasMoreData; + if (didSucceed) { + char hash[2 * sizeof(digest) + 1]; + for (size_t i = 0; i < sizeof(digest); ++i) { + snprintf(hash + (2 * i), 3, "%02x", (int)(digest[i])); + } + result = [NSString stringWithUTF8String:hash]; + } + + return result; +} + +#pragma mark - +#pragma mark public class accessors ++ (NSString *)md5HashOfData:(NSData *)data { + FileHashComputationContext context; + FileHashComputationContextInitialize(context, MD5); + return [self hashOfNSData:data withComputationContext:&context]; +} + ++ (NSString *)sha1HashOfData:(NSData *)data { + FileHashComputationContext context; + FileHashComputationContextInitialize(context, SHA1); + return [self hashOfNSData:data withComputationContext:&context]; +} + ++ (NSString *)sha512HashOfData:(NSData *)data { + FileHashComputationContext context; + FileHashComputationContextInitialize(context, SHA512); + return [self hashOfNSData:data withComputationContext:&context]; +} + ++ (NSString *)crc32HashOfData:(NSData *)data { + FileHashComputationContext context; + FileHashComputationContextInitialize(context, CRC32); + return [self hashOfNSData:data withComputationContext:&context]; +} + ++ (NSString *)md5HashOfFileAtPath:(NSString *)filePath { + FileHashComputationContext context; + FileHashComputationContextInitialize(context, MD5); + return [self hashOfFileAtPath:filePath withComputationContext:&context]; +} + ++ (NSString *)sha1HashOfFileAtPath:(NSString *)filePath { + FileHashComputationContext context; + FileHashComputationContextInitialize(context, SHA1); + return [self hashOfFileAtPath:filePath withComputationContext:&context]; +} + ++ (NSString *)sha512HashOfFileAtPath:(NSString *)filePath { + FileHashComputationContext context; + FileHashComputationContextInitialize(context, SHA512); + return [self hashOfFileAtPath:filePath withComputationContext:&context]; +} + ++ (NSString *)crc32HashOfFileAtPath:(NSString *)filePath { + FileHashComputationContext context; + FileHashComputationContextInitialize(context, CRC32); + return [self hashOfFileAtPath:filePath withComputationContext:&context]; +} + +@end diff --git a/Source/3rdParty/SSZipArchive/SSZipArchive.h b/Source/3rdParty/SSZipArchive/SSZipArchive.h new file mode 100755 index 000000000..fb8d055aa --- /dev/null +++ b/Source/3rdParty/SSZipArchive/SSZipArchive.h @@ -0,0 +1,45 @@ +// +// SSZipArchive.h +// SSZipArchive +// +// Created by Sam Soffes on 7/21/10. +// Copyright (c) Sam Soffes 2010-2011. All rights reserved. +// + +#import +#include "minizip/unzip.h" + +@protocol SSZipArchiveDelegate; + +@interface SSZipArchive : NSObject + +// Unzip ++ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination; ++ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error; + ++ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination delegate:(id)delegate; ++ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error delegate:(id)delegate; + +// Zip ++ (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)filenames; + +- (id)initWithPath:(NSString *)path; +- (BOOL)open; +- (BOOL)writeFile:(NSString *)path; +- (BOOL)writeData:(NSData *)data filename:(NSString *)filename; +- (BOOL)close; + +@end + + +@protocol SSZipArchiveDelegate + +@optional + +- (void)zipArchiveWillUnzipArchiveAtPath:(NSString *)path zipInfo:(unz_global_info)zipInfo; +- (void)zipArchiveDidUnzipArchiveAtPath:(NSString *)path zipInfo:(unz_global_info)zipInfo unzippedPath:(NSString *)unzippedPath; + +- (void)zipArchiveWillUnzipFileAtIndex:(NSInteger)fileIndex totalFiles:(NSInteger)totalFiles archivePath:(NSString *)archivePath fileInfo:(unz_file_info)fileInfo; +- (void)zipArchiveDidUnzipFileAtIndex:(NSInteger)fileIndex totalFiles:(NSInteger)totalFiles archivePath:(NSString *)archivePath fileInfo:(unz_file_info)fileInfo; + +@end diff --git a/Source/3rdParty/SSZipArchive/SSZipArchive.m b/Source/3rdParty/SSZipArchive/SSZipArchive.m new file mode 100755 index 000000000..c53c19201 --- /dev/null +++ b/Source/3rdParty/SSZipArchive/SSZipArchive.m @@ -0,0 +1,421 @@ +// +// SSZipArchive.m +// SSZipArchive +// +// Created by Sam Soffes on 7/21/10. +// Copyright (c) Sam Soffes 2010-2011. All rights reserved. +// + +#import "SSZipArchive.h" +#include "minizip/zip.h" +#import "zlib.h" +#import "zconf.h" + +#include + +#define CHUNK 16384 + +@interface SSZipArchive () ++ (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime; +@end + + +@implementation SSZipArchive { + NSString *_path; + NSString *_filename; + zipFile _zip; +} + + +#pragma mark - Unzipping + ++ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination { + return [self unzipFileAtPath:path toDestination:destination delegate:nil]; +} + + ++ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error { + return [self unzipFileAtPath:path toDestination:destination overwrite:overwrite password:password error:error delegate:nil]; +} + + ++ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination delegate:(id)delegate { + return [self unzipFileAtPath:path toDestination:destination overwrite:YES password:nil error:nil delegate:delegate]; +} + + ++ (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination overwrite:(BOOL)overwrite password:(NSString *)password error:(NSError **)error delegate:(id)delegate { + // Begin opening + zipFile zip = unzOpen((const char*)[path UTF8String]); + if (zip == NULL) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"failed to open zip file" forKey:NSLocalizedDescriptionKey]; + if (error) { + *error = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:-1 userInfo:userInfo]; + } + return NO; + } + + unz_global_info globalInfo = {0ul, 0ul}; + unzGetGlobalInfo(zip, &globalInfo); + + // Begin unzipping + if (unzGoToFirstFile(zip) != UNZ_OK) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"failed to open first file in zip file" forKey:NSLocalizedDescriptionKey]; + if (error) { + *error = [NSError errorWithDomain:@"SSZipArchiveErrorDomain" code:-2 userInfo:userInfo]; + } + return NO; + } + + BOOL success = YES; + int ret = 0; + unsigned char buffer[4096] = {0}; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSMutableSet *directoriesModificationDates = [[NSMutableSet alloc] init]; + + // Message delegate + if ([delegate respondsToSelector:@selector(zipArchiveWillUnzipArchiveAtPath:zipInfo:)]) { + [delegate zipArchiveWillUnzipArchiveAtPath:path zipInfo:globalInfo]; + } + + NSInteger currentFileNumber = 0; + do { + if ([password length] == 0) { + ret = unzOpenCurrentFile(zip); + } else { + ret = unzOpenCurrentFilePassword(zip, [password cStringUsingEncoding:NSASCIIStringEncoding]); + } + + if (ret != UNZ_OK) { + success = NO; + break; + } + + // Reading data and write to file + unz_file_info fileInfo; + memset(&fileInfo, 0, sizeof(unz_file_info)); + + ret = unzGetCurrentFileInfo(zip, &fileInfo, NULL, 0, NULL, 0, NULL, 0); + if (ret != UNZ_OK) { + success = NO; + unzCloseCurrentFile(zip); + break; + } + + // Message delegate + if ([delegate respondsToSelector:@selector(zipArchiveWillUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) { + [delegate zipArchiveWillUnzipFileAtIndex:currentFileNumber totalFiles:(NSInteger)globalInfo.number_entry + archivePath:path fileInfo:fileInfo]; + } + + char *filename = (char *)malloc(fileInfo.size_filename + 1); + unzGetCurrentFileInfo(zip, &fileInfo, filename, fileInfo.size_filename + 1, NULL, 0, NULL, 0); + filename[fileInfo.size_filename] = '\0'; + + // + // NOTE + // I used the ZIP spec from here: + // http://www.pkware.com/documents/casestudies/APPNOTE.TXT + // + // ...to deduce this method of detecting whether the file in the ZIP is a symbolic link. + // If it is, it is listed as a directory but has a data size greater than zero (real + // directories have it equal to 0) and the included, uncompressed data is the symbolic link path. + // + // ZIP files did not originally include support for symbolic links so the specification + // doesn't include anything in them that isn't part of a unix extension that isn't being used + // by the archivers we're testing. Most of this is figured out through trial and error and + // reading ZIP headers in hex editors. This seems to do the trick though. + // + + const uLong ZipCompressionMethodStore = 0; + + BOOL fileIsSymbolicLink = NO; + + if((fileInfo.compression_method == ZipCompressionMethodStore) && // Is it compressed? + (S_ISDIR(fileInfo.external_fa)) && // Is it marked as a directory + (fileInfo.compressed_size > 0)) // Is there any data? + { + fileIsSymbolicLink = YES; + } + + // Check if it contains directory + NSString *strPath = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding]; + BOOL isDirectory = NO; + if (filename[fileInfo.size_filename-1] == '/' || filename[fileInfo.size_filename-1] == '\\') { + isDirectory = YES; + } + free(filename); + + // Contains a path + if ([strPath rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"/\\"]].location != NSNotFound) { + strPath = [strPath stringByReplacingOccurrencesOfString:@"\\" withString:@"/"]; + } + + NSString *fullPath = [destination stringByAppendingPathComponent:strPath]; + NSError *err = nil; + NSDate *modDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate]; + NSDictionary *directoryAttr = [NSDictionary dictionaryWithObjectsAndKeys:modDate, NSFileCreationDate, modDate, NSFileModificationDate, nil]; + + if (isDirectory) { + [fileManager createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:directoryAttr error:&err]; + } else { + [fileManager createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:directoryAttr error:&err]; + } + if (nil != err) { + NSLog(@"[SSZipArchive] Error: %@", err.localizedDescription); + } + + if(!fileIsSymbolicLink) + [directoriesModificationDates addObject: [NSDictionary dictionaryWithObjectsAndKeys:fullPath, @"path", modDate, @"modDate", nil]]; + + if ([fileManager fileExistsAtPath:fullPath] && !isDirectory && !overwrite) { + unzCloseCurrentFile(zip); + ret = unzGoToNextFile(zip); + continue; + } + + if(!fileIsSymbolicLink) + { + FILE *fp = fopen((const char*)[fullPath UTF8String], "wb"); + while (fp) { + int readBytes = unzReadCurrentFile(zip, buffer, 4096); + + if (readBytes > 0) { + fwrite(buffer, readBytes, 1, fp ); + } else { + break; + } + } + + if (fp) { + fclose(fp); + + // Set the original datetime property + if (fileInfo.dosDate != 0) { + NSDate *orgDate = [[self class] _dateWithMSDOSFormat:(UInt32)fileInfo.dosDate]; + NSDictionary *attr = [NSDictionary dictionaryWithObject:orgDate forKey:NSFileModificationDate]; + + if (attr) { + if ([fileManager setAttributes:attr ofItemAtPath:fullPath error:nil] == NO) { + // Can't set attributes + NSLog(@"[SSZipArchive] Failed to set attributes"); + } + } + } + } + } + else + { + // Get the path for the symbolic link + + NSURL* symlinkURL = [NSURL fileURLWithPath:fullPath]; + NSMutableString* destinationPath = [NSMutableString string]; + + int bytesRead = 0; + while((bytesRead = unzReadCurrentFile(zip, buffer, 4096)) > 0) + { + buffer[bytesRead] = 0; + [destinationPath appendString:[NSString stringWithUTF8String:(const char*)buffer]]; + } + + //NSLog(@"Symlinking to: %@", destinationPath); + + NSURL* destinationURL = [NSURL fileURLWithPath:destinationPath]; + + // Create the symbolic link + NSError* symlinkError = nil; + [fileManager createSymbolicLinkAtURL:symlinkURL withDestinationURL:destinationURL error:&symlinkError]; + + if(symlinkError != nil) + { + NSLog(@"Failed to create symbolic link at \"%@\" to \"%@\". Error: %@", symlinkURL.absoluteString, destinationURL.absoluteString, symlinkError.localizedDescription); + } + } + + unzCloseCurrentFile( zip ); + ret = unzGoToNextFile( zip ); + + // Message delegate + if ([delegate respondsToSelector:@selector(zipArchiveDidUnzipFileAtIndex:totalFiles:archivePath:fileInfo:)]) { + [delegate zipArchiveDidUnzipFileAtIndex:currentFileNumber totalFiles:(NSInteger)globalInfo.number_entry + archivePath:path fileInfo:fileInfo]; + } + + currentFileNumber++; + } while(ret == UNZ_OK && UNZ_OK != UNZ_END_OF_LIST_OF_FILE); + + // Close + unzClose(zip); + + // The process of decompressing the .zip archive causes the modification times on the folders + // to be set to the present time. So, when we are done, they need to be explicitly set. + // set the modification date on all of the directories. + NSError * err = nil; + for (NSDictionary * d in directoriesModificationDates) { + if (![[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[d objectForKey:@"modDate"], NSFileModificationDate, nil] ofItemAtPath:[d objectForKey:@"path"] error:&err]) { + NSLog(@"[SSZipArchive] Set attributes failed for directory: %@.", [d objectForKey:@"path"]); + } + if (err) { + NSLog(@"[SSZipArchive] Error setting directory file modification date attribute: %@",err.localizedDescription); + } + } + +#if !__has_feature(objc_arc) + [directoriesModificationDates release]; +#endif + + // Message delegate + if (success && [delegate respondsToSelector:@selector(zipArchiveDidUnzipArchiveAtPath:zipInfo:unzippedPath:)]) { + [delegate zipArchiveDidUnzipArchiveAtPath:path zipInfo:globalInfo unzippedPath:destination]; + } + + return success; +} + + +#pragma mark - Zipping + ++ (BOOL)createZipFileAtPath:(NSString *)path withFilesAtPaths:(NSArray *)paths { + BOOL success = NO; + SSZipArchive *zipArchive = [[SSZipArchive alloc] initWithPath:path]; + if ([zipArchive open]) { + for (path in paths) { + [zipArchive writeFile:path]; + } + success = [zipArchive close]; + } + +#if !__has_feature(objc_arc) + [zipArchive release]; +#endif + + return success; +} + + +- (id)initWithPath:(NSString *)path { + if ((self = [super init])) { + _path = [path copy]; + } + return self; +} + + +#if !__has_feature(objc_arc) +- (void)dealloc { + [_path release]; + [super dealloc]; +} +#endif + + +- (BOOL)open { + NSAssert((_zip == NULL), @"Attempting open an archive which is already open"); + _zip = zipOpen([_path UTF8String], APPEND_STATUS_CREATE); + return (NULL != _zip); +} + + +- (void)zipInfo:(zip_fileinfo*)zipInfo setDate:(NSDate*)date { + NSCalendar *currentCalendar = [NSCalendar currentCalendar]; + uint flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; + NSDateComponents *components = [currentCalendar components:flags fromDate:date]; + zipInfo->tmz_date.tm_sec = (unsigned int)components.second; + zipInfo->tmz_date.tm_min = (unsigned int)components.minute; + zipInfo->tmz_date.tm_hour = (unsigned int)components.hour; + zipInfo->tmz_date.tm_mday = (unsigned int)components.day; + zipInfo->tmz_date.tm_mon = (unsigned int)components.month - 1; + zipInfo->tmz_date.tm_year = (unsigned int)components.year; +} + + +- (BOOL)writeFile:(NSString *)path { + NSAssert((_zip != NULL), @"Attempting to write to an archive which was never opened"); + + FILE *input = fopen([path UTF8String], "r"); + if (NULL == input) { + return NO; + } + + zipOpenNewFileInZip(_zip, [[path lastPathComponent] UTF8String], NULL, NULL, 0, NULL, 0, NULL, Z_DEFLATED, + Z_DEFAULT_COMPRESSION); + + void *buffer = malloc(CHUNK); + unsigned int len = 0; + while (!feof(input)) { + len = (unsigned int) fread(buffer, 1, CHUNK, input); + zipWriteInFileInZip(_zip, buffer, len); + } + + zipCloseFileInZip(_zip); + free(buffer); + return YES; +} + + +- (BOOL)writeData:(NSData *)data filename:(NSString *)filename { + if (!_zip) { + return NO; + } + if (!data) { + return NO; + } + zip_fileinfo zipInfo = {{0,0,0,0,0,0},0,0,0}; + [self zipInfo:&zipInfo setDate:[NSDate date]]; + + zipOpenNewFileInZip(_zip, [filename UTF8String], &zipInfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION); + + zipWriteInFileInZip(_zip, data.bytes, (unsigned int)data.length); + + zipCloseFileInZip(_zip); + return YES; +} + + +- (BOOL)close { + NSAssert((_zip != NULL), @"[SSZipArchive] Attempting to close an archive which was never opened"); + zipClose(_zip, NULL); + return YES; +} + + +#pragma mark - Private + +// Format from http://newsgroups.derkeiler.com/Archive/Comp/comp.os.msdos.programmer/2009-04/msg00060.html +// Two consecutive words, or a longword, YYYYYYYMMMMDDDDD hhhhhmmmmmmsssss +// YYYYYYY is years from 1980 = 0 +// sssss is (seconds/2). +// +// 3658 = 0011 0110 0101 1000 = 0011011 0010 11000 = 27 2 24 = 2007-02-24 +// 7423 = 0111 0100 0010 0011 - 01110 100001 00011 = 14 33 2 = 14:33:06 ++ (NSDate *)_dateWithMSDOSFormat:(UInt32)msdosDateTime { + static const UInt32 kYearMask = 0xFE000000; + static const UInt32 kMonthMask = 0x1E00000; + static const UInt32 kDayMask = 0x1F0000; + static const UInt32 kHourMask = 0xF800; + static const UInt32 kMinuteMask = 0x7E0; + static const UInt32 kSecondMask = 0x1F; + + NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; + NSDateComponents *components = [[NSDateComponents alloc] init]; + + NSAssert(0xFFFFFFFF == (kYearMask | kMonthMask | kDayMask | kHourMask | kMinuteMask | kSecondMask), @"[SSZipArchive] MSDOS date masks don't add up"); + + [components setYear:1980 + ((msdosDateTime & kYearMask) >> 25)]; + [components setMonth:(msdosDateTime & kMonthMask) >> 21]; + [components setDay:(msdosDateTime & kDayMask) >> 16]; + [components setHour:(msdosDateTime & kHourMask) >> 11]; + [components setMinute:(msdosDateTime & kMinuteMask) >> 5]; + [components setSecond:(msdosDateTime & kSecondMask) * 2]; + + NSDate *date = [NSDate dateWithTimeInterval:0 sinceDate:[gregorian dateFromComponents:components]]; + +#if !__has_feature(objc_arc) + [gregorian release]; + [components release]; +#endif + + return date; +} + +@end diff --git a/Source/3rdParty/SSZipArchive/minizip/crypt.h b/Source/3rdParty/SSZipArchive/minizip/crypt.h new file mode 100755 index 000000000..a01d08d93 --- /dev/null +++ b/Source/3rdParty/SSZipArchive/minizip/crypt.h @@ -0,0 +1,131 @@ +/* crypt.h -- base code for crypt/uncrypt ZIPfile + + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This code is a modified version of crypting code in Infozip distribution + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + If you don't need crypting in your application, just define symbols + NOCRYPT and NOUNCRYPT. + + This code support the "Traditional PKWARE Encryption". + + The new AES encryption added on Zip format by Winzip (see the page + http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong + Encryption is not supported. +*/ + +#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) + +/*********************************************************************** + * Return the next byte in the pseudo-random sequence + */ +static int decrypt_byte(unsigned long* pkeys, const unsigned long* pcrc_32_tab) +{ + unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an + * unpredictable manner on 16-bit systems; not a problem + * with any known compiler so far, though */ + + temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; + return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +/*********************************************************************** + * Update the encryption keys with the next byte of plain text + */ +static int update_keys(unsigned long* pkeys,const unsigned long* pcrc_32_tab,int c) +{ + (*(pkeys+0)) = CRC32((*(pkeys+0)), c); + (*(pkeys+1)) += (*(pkeys+0)) & 0xff; + (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; + { + register int keyshift = (int)((*(pkeys+1)) >> 24); + (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); + } + return c; +} + + +/*********************************************************************** + * Initialize the encryption keys and the random header according to + * the given password. + */ +static void init_keys(const char* passwd,unsigned long* pkeys,const unsigned long* pcrc_32_tab) +{ + *(pkeys+0) = 305419896L; + *(pkeys+1) = 591751049L; + *(pkeys+2) = 878082192L; + while (*passwd != '\0') { + update_keys(pkeys,pcrc_32_tab,(int)*passwd); + passwd++; + } +} + +#define zdecode(pkeys,pcrc_32_tab,c) \ + (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) + +#define zencode(pkeys,pcrc_32_tab,c,t) \ + (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) + +#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED + +#define RAND_HEAD_LEN 12 + /* "last resort" source for second part of crypt seed pattern */ +# ifndef ZCR_SEED2 +# define ZCR_SEED2 3141592654UL /* use PI as default pattern */ +# endif + +static int crypthead(const char* passwd, /* password string */ + unsigned char* buf, /* where to write header */ + int bufSize, + unsigned long* pkeys, + const unsigned long* pcrc_32_tab, + unsigned long crcForCrypting) +{ + int n; /* index in random header */ + int t; /* temporary */ + int c; /* random byte */ + unsigned char header[RAND_HEAD_LEN-2]; /* random header */ + static unsigned calls = 0; /* ensure different random header each time */ + + if (bufSize> 7) & 0xff; + header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); + } + /* Encrypt random header (last two bytes is high word of crc) */ + init_keys(passwd, pkeys, pcrc_32_tab); + for (n = 0; n < RAND_HEAD_LEN-2; n++) + { + buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); + } + buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); + buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); + return n; +} + +#endif diff --git a/Source/3rdParty/SSZipArchive/minizip/ioapi.h b/Source/3rdParty/SSZipArchive/minizip/ioapi.h new file mode 100755 index 000000000..7e20e9513 --- /dev/null +++ b/Source/3rdParty/SSZipArchive/minizip/ioapi.h @@ -0,0 +1,201 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + Changes + + Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this) + Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux. + More if/def section may be needed to support other platforms + Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows. + (but you should use iowin32.c for windows instead) + +*/ + +#ifndef _ZLIBIOAPI64_H +#define _ZLIBIOAPI64_H + +#if (!defined(_WIN32)) && (!defined(WIN32)) + + // Linux needs this to support file operation on files larger then 4+GB + // But might need better if/def to select just the platforms that needs them. + + #ifndef __USE_FILE_OFFSET64 + #define __USE_FILE_OFFSET64 + #endif + #ifndef __USE_LARGEFILE64 + #define __USE_LARGEFILE64 + #endif + #ifndef _LARGEFILE64_SOURCE + #define _LARGEFILE64_SOURCE + #endif + #ifndef _FILE_OFFSET_BIT + #define _FILE_OFFSET_BIT 64 + #endif +#endif + +#include +#include +#include "zlib.h" + +#define USE_FILE32API +#if defined(USE_FILE32API) +#define fopen64 fopen +#define ftello64 ftell +#define fseeko64 fseek +#else +#ifdef _MSC_VER + #define fopen64 fopen + #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC))) + #define ftello64 _ftelli64 + #define fseeko64 _fseeki64 + #else // old MSC + #define ftello64 ftell + #define fseeko64 fseek + #endif +#endif +#endif + +/* +#ifndef ZPOS64_T + #ifdef _WIN32 + #define ZPOS64_T fpos_t + #else + #include + #define ZPOS64_T uint64_t + #endif +#endif +*/ + +#ifdef HAVE_MINIZIP64_CONF_H +#include "mz64conf.h" +#endif + +/* a type choosen by DEFINE */ +#ifdef HAVE_64BIT_INT_CUSTOM +typedef 64BIT_INT_CUSTOM_TYPE ZPOS64_T; +#else +#ifdef HAS_STDINT_H +#include "stdint.h" +typedef uint64_t ZPOS64_T; +#else + + +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef unsigned __int64 ZPOS64_T; +#else +typedef unsigned long long int ZPOS64_T; +#endif +#endif +#endif + + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ (1) +#define ZLIB_FILEFUNC_MODE_WRITE (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) + +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE (8) + + +#ifndef ZCALLBACK + #if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) + #define ZCALLBACK CALLBACK + #else + #define ZCALLBACK + #endif +#endif + + + + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); +typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); + + +/* here is the "old" 32 bits structure structure */ +typedef struct zlib_filefunc_def_s +{ + open_file_func zopen_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell_file_func ztell_file; + seek_file_func zseek_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc_def; + +typedef ZPOS64_T (ZCALLBACK *tell64_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek64_file_func) OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); +typedef voidpf (ZCALLBACK *open64_file_func) OF((voidpf opaque, const void* filename, int mode)); + +typedef struct zlib_filefunc64_def_s +{ + open64_file_func zopen64_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell64_file_func ztell64_file; + seek64_file_func zseek64_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc64_def; + +void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def)); +void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +/* now internal definition, only for zip.c and unzip.h */ +typedef struct zlib_filefunc64_32_def_s +{ + zlib_filefunc64_def zfile_func64; + open_file_func zopen32_file; + tell_file_func ztell32_file; + seek_file_func zseek32_file; +} zlib_filefunc64_32_def; + + +#define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) +#define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) +//#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream)) +//#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode)) +#define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream)) +#define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream)) + +voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)); +long call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)); +ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)); + +void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32); + +#define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode))) +#define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream))) +#define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode))) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Source/3rdParty/SSZipArchive/minizip/ioapi.m b/Source/3rdParty/SSZipArchive/minizip/ioapi.m new file mode 100755 index 000000000..e0f738297 --- /dev/null +++ b/Source/3rdParty/SSZipArchive/minizip/ioapi.m @@ -0,0 +1,239 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + +*/ + +#if (defined(_WIN32)) + #define _CRT_SECURE_NO_WARNINGS +#endif + +#include "ioapi.h" + +voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode) +{ + if (pfilefunc->zfile_func64.zopen64_file != NULL) + return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode); + else + { + return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode); + } +} + +long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin) +{ + if (pfilefunc->zfile_func64.zseek64_file != NULL) + return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin); + else + { + uLong offsetTruncated = (uLong)offset; + if (offsetTruncated != offset) + return -1; + else + return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin); + } +} + +ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream) +{ + if (pfilefunc->zfile_func64.zseek64_file != NULL) + return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream); + else + { + uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream); + if ((tell_uLong) == ((uLong)-1)) + return (ZPOS64_T)-1; + else + return tell_uLong; + } +} + +void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32) +{ + p_filefunc64_32->zfile_func64.zopen64_file = NULL; + p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file; + p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; + p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file; + p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file; + p_filefunc64_32->zfile_func64.ztell64_file = NULL; + p_filefunc64_32->zfile_func64.zseek64_file = NULL; + p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file; + +#ifndef __clang_analyzer__ + p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; +#endif + + p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque; + p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file; + p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file; +} + + + +static voidpf ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode)); +static uLong ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +static uLong ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size)); +static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream)); +static long ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); +static int ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream)); +static int ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream)); + +static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode) +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename!=NULL) && (mode_fopen != NULL)) + file = fopen(filename, mode_fopen); + return file; +} + +static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode) +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename!=NULL) && (mode_fopen != NULL)) + file = fopen64((const char*)filename, mode_fopen); + return file; +} + + +static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size) +{ + uLong ret; + ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + +static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size) +{ + uLong ret; + ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + +static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream) +{ + long ret; + ret = ftell((FILE *)stream); + return ret; +} + + +static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream) +{ + ZPOS64_T ret; + ret = ftello64((FILE *)stream); + return ret; +} + +static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offset, int origin) +{ + int fseek_origin=0; + long ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END : + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + fseek_origin = SEEK_SET; + break; + default: return -1; + } + ret = 0; + if (fseek((FILE *)stream, offset, fseek_origin) != 0) + ret = -1; + return ret; +} + +static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) +{ + int fseek_origin=0; + long ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END : + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + fseek_origin = SEEK_SET; + break; + default: return -1; + } + ret = 0; + + if(fseeko64((FILE *)stream, offset, fseek_origin) != 0) + ret = -1; + + return ret; +} + + +static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream) +{ + int ret; + ret = fclose((FILE *)stream); + return ret; +} + +static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream) +{ + int ret; + ret = ferror((FILE *)stream); + return ret; +} + +void fill_fopen_filefunc (pzlib_filefunc_def) + zlib_filefunc_def* pzlib_filefunc_def; +{ + pzlib_filefunc_def->zopen_file = fopen_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell_file = ftell_file_func; + pzlib_filefunc_def->zseek_file = fseek_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} + +void fill_fopen64_filefunc (zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = fopen64_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell64_file = ftell64_file_func; + pzlib_filefunc_def->zseek64_file = fseek64_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} diff --git a/Source/3rdParty/SSZipArchive/minizip/mztools.h b/Source/3rdParty/SSZipArchive/minizip/mztools.h new file mode 100755 index 000000000..88b34592b --- /dev/null +++ b/Source/3rdParty/SSZipArchive/minizip/mztools.h @@ -0,0 +1,31 @@ +/* + Additional tools for Minizip + Code: Xavier Roche '2004 + License: Same as ZLIB (www.gzip.org) +*/ + +#ifndef _zip_tools_H +#define _zip_tools_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#include "unzip.h" + +/* Repair a ZIP file (missing central directory) + file: file to recover + fileOut: output file after recovery + fileOutTmp: temporary file name used for recovery +*/ +extern int ZEXPORT unzRepair(const char* file, + const char* fileOut, + const char* fileOutTmp, + uLong* nRecovered, + uLong* bytesRecovered); + +#endif diff --git a/Source/3rdParty/SSZipArchive/minizip/mztools.m b/Source/3rdParty/SSZipArchive/minizip/mztools.m new file mode 100755 index 000000000..80d50e008 --- /dev/null +++ b/Source/3rdParty/SSZipArchive/minizip/mztools.m @@ -0,0 +1,284 @@ +/* + Additional tools for Minizip + Code: Xavier Roche '2004 + License: Same as ZLIB (www.gzip.org) +*/ + +/* Code */ +#include +#include +#include +#include "zlib.h" +#include "unzip.h" +#include "mztools.h" + +#define READ_8(adr) ((unsigned char)*(adr)) +#define READ_16(adr) ( READ_8(adr) | (READ_8(adr+1) << 8) ) +#define READ_32(adr) ( READ_16(adr) | (READ_16((adr)+2) << 16) ) + +#define WRITE_8(buff, n) do { \ + *((unsigned char*)(buff)) = (unsigned char) ((n) & 0xff); \ +} while(0) +#define WRITE_16(buff, n) do { \ + WRITE_8((unsigned char*)(buff), n); \ + WRITE_8(((unsigned char*)(buff)) + 1, (n) >> 8); \ +} while(0) +#define WRITE_32(buff, n) do { \ + WRITE_16((unsigned char*)(buff), (n) & 0xffff); \ + WRITE_16((unsigned char*)(buff) + 2, (n) >> 16); \ +} while(0) + +extern int ZEXPORT unzRepair(file, fileOut, fileOutTmp, nRecovered, bytesRecovered) +const char* file; +const char* fileOut; +const char* fileOutTmp; +uLong* nRecovered; +uLong* bytesRecovered; +{ + int err = Z_OK; + FILE* fpZip = fopen(file, "rb"); + FILE* fpOut = fopen(fileOut, "wb"); + FILE* fpOutCD = fopen(fileOutTmp, "wb"); + if (fpZip != NULL && fpOut != NULL) { + int entries = 0; + uLong totalBytes = 0; + char header[30]; + char filename[256]; + char extra[1024]; + int offset = 0; + int offsetCD = 0; + while ( fread(header, 1, 30, fpZip) == 30 ) { + int currentOffset = offset; + + /* File entry */ + if (READ_32(header) == 0x04034b50) { + unsigned int version = READ_16(header + 4); + unsigned int gpflag = READ_16(header + 6); + unsigned int method = READ_16(header + 8); + unsigned int filetime = READ_16(header + 10); + unsigned int filedate = READ_16(header + 12); + unsigned int crc = READ_32(header + 14); /* crc */ + unsigned int cpsize = READ_32(header + 18); /* compressed size */ + unsigned int uncpsize = READ_32(header + 22); /* uncompressed sz */ + unsigned int fnsize = READ_16(header + 26); /* file name length */ + unsigned int extsize = READ_16(header + 28); /* extra field length */ + filename[0] = extra[0] = '\0'; + + /* Header */ + if (fwrite(header, 1, 30, fpOut) == 30) { + offset += 30; + } else { + err = Z_ERRNO; + break; + } + + /* Filename */ + if (fnsize > 0) { + if (fread(filename, 1, fnsize, fpZip) == fnsize) { + if (fwrite(filename, 1, fnsize, fpOut) == fnsize) { + offset += fnsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_STREAM_ERROR; + break; + } + + /* Extra field */ + if (extsize > 0) { + if (fread(extra, 1, extsize, fpZip) == extsize) { + if (fwrite(extra, 1, extsize, fpOut) == extsize) { + offset += extsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } + + /* Data */ + { + int dataSize = cpsize; + if (dataSize == 0) { + dataSize = uncpsize; + } + if (dataSize > 0) { + char* data = malloc(dataSize); + if (data != NULL) { + if ((int)fread(data, 1, dataSize, fpZip) == dataSize) { + if ((int)fwrite(data, 1, dataSize, fpOut) == dataSize) { + offset += dataSize; + totalBytes += dataSize; + } else { + err = Z_ERRNO; + } + } else { + err = Z_ERRNO; + } + free(data); + if (err != Z_OK) { + break; + } + } else { + err = Z_MEM_ERROR; + break; + } + } + } + + /* Central directory entry */ + { + char centralDirectoryEntryHeader[46]; + //char* comment = ""; + //int comsize = (int) strlen(comment); + WRITE_32(centralDirectoryEntryHeader, 0x02014b50); + WRITE_16(centralDirectoryEntryHeader + 4, version); + WRITE_16(centralDirectoryEntryHeader + 6, version); + WRITE_16(centralDirectoryEntryHeader + 8, gpflag); + WRITE_16(centralDirectoryEntryHeader + 10, method); + WRITE_16(centralDirectoryEntryHeader + 12, filetime); + WRITE_16(centralDirectoryEntryHeader + 14, filedate); + WRITE_32(centralDirectoryEntryHeader + 16, crc); + WRITE_32(centralDirectoryEntryHeader + 20, cpsize); + WRITE_32(centralDirectoryEntryHeader + 24, uncpsize); + WRITE_16(centralDirectoryEntryHeader + 28, fnsize); + WRITE_16(centralDirectoryEntryHeader + 30, extsize); + WRITE_16(centralDirectoryEntryHeader + 32, 0 /*comsize*/); + WRITE_16(centralDirectoryEntryHeader + 34, 0); /* disk # */ + WRITE_16(centralDirectoryEntryHeader + 36, 0); /* int attrb */ + WRITE_32(centralDirectoryEntryHeader + 38, 0); /* ext attrb */ + WRITE_32(centralDirectoryEntryHeader + 42, currentOffset); + /* Header */ + if (fwrite(centralDirectoryEntryHeader, 1, 46, fpOutCD) == 46) { + offsetCD += 46; + + /* Filename */ + if (fnsize > 0) { + if (fwrite(filename, 1, fnsize, fpOutCD) == fnsize) { + offsetCD += fnsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_STREAM_ERROR; + break; + } + + /* Extra field */ + if (extsize > 0) { + if (fwrite(extra, 1, extsize, fpOutCD) == extsize) { + offsetCD += extsize; + } else { + err = Z_ERRNO; + break; + } + } + + /* Comment field */ + /* + if (comsize > 0) { + if ((int)fwrite(comment, 1, comsize, fpOutCD) == comsize) { + offsetCD += comsize; + } else { + err = Z_ERRNO; + break; + } + } + */ + + } else { + err = Z_ERRNO; + break; + } + } + + /* Success */ + entries++; + + } else { + break; + } + } + + /* Final central directory */ + { + int entriesZip = entries; + char finalCentralDirectoryHeader[22]; + //char* comment = ""; // "ZIP File recovered by zlib/minizip/mztools"; + //int comsize = (int) strlen(comment); + if (entriesZip > 0xffff) { + entriesZip = 0xffff; + } + WRITE_32(finalCentralDirectoryHeader, 0x06054b50); + WRITE_16(finalCentralDirectoryHeader + 4, 0); /* disk # */ + WRITE_16(finalCentralDirectoryHeader + 6, 0); /* disk # */ + WRITE_16(finalCentralDirectoryHeader + 8, entriesZip); /* hack */ + WRITE_16(finalCentralDirectoryHeader + 10, entriesZip); /* hack */ + WRITE_32(finalCentralDirectoryHeader + 12, offsetCD); /* size of CD */ + WRITE_32(finalCentralDirectoryHeader + 16, offset); /* offset to CD */ + WRITE_16(finalCentralDirectoryHeader + 20, 0 /*comsize*/); /* comment */ + + /* Header */ + if (fwrite(finalCentralDirectoryHeader, 1, 22, fpOutCD) == 22) { + + /* Comment field */ + /* + if (comsize > 0) { + if ((int)fwrite(comment, 1, comsize, fpOutCD) != comsize) { + err = Z_ERRNO; + } + } + */ + } else { + err = Z_ERRNO; + } + } + + /* Final merge (file + central directory) */ + fclose(fpOutCD); + if (err == Z_OK) { + fpOutCD = fopen(fileOutTmp, "rb"); + if (fpOutCD != NULL) { + int nRead; + char buffer[8192]; + while ( (nRead = (int)fread(buffer, 1, sizeof(buffer), fpOutCD)) > 0) { + if ((int)fwrite(buffer, 1, nRead, fpOut) != nRead) { + err = Z_ERRNO; + break; + } + } + fclose(fpOutCD); + } + } + + /* Close */ + fclose(fpZip); + fclose(fpOut); + + /* Wipe temporary file */ + (void)remove(fileOutTmp); + + /* Number of recovered entries */ + if (err == Z_OK) { + if (nRecovered != NULL) { + *nRecovered = entries; + } + if (bytesRecovered != NULL) { + *bytesRecovered = totalBytes; + } + } + } else { + err = Z_STREAM_ERROR; + } + return err; +} diff --git a/Source/3rdParty/SSZipArchive/minizip/unzip.h b/Source/3rdParty/SSZipArchive/minizip/unzip.h new file mode 100755 index 000000000..3183968b7 --- /dev/null +++ b/Source/3rdParty/SSZipArchive/minizip/unzip.h @@ -0,0 +1,437 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + --------------------------------------------------------------------------------- + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + --------------------------------------------------------------------------------- + + Changes + + See header of unzip64.c + +*/ + +#ifndef _unz64_H +#define _unz64_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#ifdef HAVE_BZIP2 +#include "bzlib.h" +#endif + +#define Z_BZIP2ED 12 + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef voidp unzFile; +#endif + + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info64_s +{ + ZPOS64_T number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info64; + +typedef struct unz_global_info_s +{ + uLong number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info64_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + ZPOS64_T compressed_size; /* compressed size 8 bytes */ + ZPOS64_T uncompressed_size; /* uncompressed size 8 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info64; + +typedef struct unz_file_info_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + uLong compressed_size; /* compressed size 4 bytes */ + uLong uncompressed_size; /* uncompressed size 4 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info; + +extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, + const char* fileName2, + int iCaseSensitivity)); +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + + +extern unzFile ZEXPORT unzOpen OF((const char *path)); +extern unzFile ZEXPORT unzOpen64 OF((const void *path)); +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer + "zlib/zlib113.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. + the "64" function take a const void* pointer, because the path is just the + value passed to the open64_file_func callback. + Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path + is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char* + does not describe the reality +*/ + + +extern unzFile ZEXPORT unzOpen2 OF((const char *path, + zlib_filefunc_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unzOpen, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern unzFile ZEXPORT unzOpen2_64 OF((const void *path, + zlib_filefunc64_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unz64Open, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern int ZEXPORT unzClose OF((unzFile file)); +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, + unz_global_info *pglobal_info)); + +extern int ZEXPORT unzGetGlobalInfo64 OF((unzFile file, + unz_global_info64 *pglobal_info)); +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int ZEXPORT unzGetGlobalComment OF((unzFile file, + char *szComment, + uLong uSizeBuf)); +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int ZEXPORT unzGoToNextFile OF((unzFile file)); +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int ZEXPORT unzLocateFile OF((unzFile file, + const char *szFileName, + int iCaseSensitivity)); +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +/* ****************************************** */ +/* Ryan supplied functions */ +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; /* offset in zip file directory */ + uLong num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos); + +typedef struct unz64_file_pos_s +{ + ZPOS64_T pos_in_zip_directory; /* offset in zip file directory */ + ZPOS64_T num_of_file; /* # of file */ +} unz64_file_pos; + +extern int ZEXPORT unzGetFilePos64( + unzFile file, + unz64_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos64( + unzFile file, + const unz64_file_pos* file_pos); + +/* ****************************************** */ + +extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file, + unz_file_info64 *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + + +/** Addition for GDAL : START */ + +extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64 OF((unzFile file)); + +/** Addition for GDAL : END */ + + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, + const char* password)); +/* + Open for reading data the current file in the zipfile. + password is a crypting password + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, + int* method, + int* level, + int raw)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, + int* method, + int* level, + int raw, + const char* password)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + + +extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + +extern int ZEXPORT unzReadCurrentFile OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern z_off_t ZEXPORT unztell OF((unzFile file)); + +extern ZPOS64_T ZEXPORT unztell64 OF((unzFile file)); +/* + Give the current position in uncompressed data +*/ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ + +/***************************************************************************/ + +/* Get the current file offset */ +extern ZPOS64_T ZEXPORT unzGetOffset64 (unzFile file); +extern uLong ZEXPORT unzGetOffset (unzFile file); + +/* Set the current file offset */ +extern int ZEXPORT unzSetOffset64 (unzFile file, ZPOS64_T pos); +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _unz64_H */ diff --git a/Source/3rdParty/SSZipArchive/minizip/unzip.m b/Source/3rdParty/SSZipArchive/minizip/unzip.m new file mode 100755 index 000000000..667189c2e --- /dev/null +++ b/Source/3rdParty/SSZipArchive/minizip/unzip.m @@ -0,0 +1,2150 @@ +/* unzip.c -- IO for uncompress .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + + ------------------------------------------------------------------------------------ + Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of + compatibility with older software. The following is from the original crypt.c. + Code woven in by Terry Thorsen 1/2003. + + Copyright (c) 1990-2000 Info-ZIP. All rights reserved. + + See the accompanying file LICENSE, version 2000-Apr-09 or later + (the contents of which are also included in zip.h) for terms of use. + If, for some reason, all these files are missing, the Info-ZIP license + also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html + + crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + ------------------------------------------------------------------------------------ + + Changes in unzip.c + + 2007-2008 - Even Rouault - Addition of cpl_unzGetCurrentFileZStreamPos + 2007-2008 - Even Rouault - Decoration of symbol names unz* -> cpl_unz* + 2007-2008 - Even Rouault - Remove old C style function prototypes + 2007-2008 - Even Rouault - Add unzip support for ZIP64 + + Copyright (C) 2007-2008 Even Rouault + + + Oct-2009 - Mathias Svensson - Removed cpl_* from symbol names (Even Rouault added them but since this is now moved to a new project (minizip64) I renamed them again). + Oct-2009 - Mathias Svensson - Fixed problem if uncompressed size was > 4G and compressed size was <4G + should only read the compressed/uncompressed size from the Zip64 format if + the size from normal header was 0xFFFFFFFF + Oct-2009 - Mathias Svensson - Applied some bug fixes from paches recived from Gilles Vollant + Oct-2009 - Mathias Svensson - Applied support to unzip files with compression mathod BZIP2 (bzip2 lib is required) + Patch created by Daniel Borca + + Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer + + Copyright (C) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson + +*/ + + +#include +#include +#include + +//#ifndef NOUNCRYPT +// #define NOUNCRYPT +//#endif + +#include "zlib.h" +#include "unzip.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + +extern int errno; +#else +# include +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + + +#ifndef CASESENSITIVITYDEFAULT_NO +# if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) +# define CASESENSITIVITYDEFAULT_NO +# endif +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (16384) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + +const char unz_copyright[] = + " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info64_internal_s +{ + ZPOS64_T offset_curfile;/* relative offset of local header 8 bytes */ +} unz_file_info64_internal; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + +#ifdef HAVE_BZIP2 + bz_stream bstream; /* bzLib stream structure for bziped */ +#endif + + ZPOS64_T pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ + uLong stream_initialised; /* flag set if stream structure is initialised*/ + + ZPOS64_T offset_local_extrafield;/* offset of the local extra field */ + uInt size_local_extrafield;/* size of the local extra field */ + ZPOS64_T pos_local_extrafield; /* position in the local extra field in read*/ + ZPOS64_T total_out_64; + + uLong crc32; /* crc32 of all data uncompressed */ + uLong crc32_wait; /* crc32 we must obtain after decompress all */ + ZPOS64_T rest_read_compressed; /* number of byte to be decompressed */ + ZPOS64_T rest_read_uncompressed;/*number of byte to be obtained after decomp*/ + zlib_filefunc64_32_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + uLong compression_method; /* compression method (0==store) */ + ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + int raw; +} file_in_zip64_read_info_s; + + +/* unz64_s contain internal information about the zipfile +*/ +typedef struct +{ + zlib_filefunc64_32_def z_filefunc; + int is64bitOpenFunction; + voidpf filestream; /* io structore of the zipfile */ + unz_global_info64 gi; /* public global information */ + ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + ZPOS64_T num_file; /* number of the current file in the zipfile*/ + ZPOS64_T pos_in_central_dir; /* pos of the current file in the central dir*/ + ZPOS64_T current_file_ok; /* flag about the usability of the current file*/ + ZPOS64_T central_pos; /* position of the beginning of the central dir*/ + + ZPOS64_T size_central_dir; /* size of the central directory */ + ZPOS64_T offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info64 cur_file_info; /* public info about the current file in zip*/ + unz_file_info64_internal cur_file_info_internal; /* private info about it*/ + file_in_zip64_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ + int encrypted; + + int isZip64; + +# ifndef NOUNCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned long* pcrc_32_tab; +# endif +} unz64_s; + + +#ifndef NOUNCRYPT +#include "crypt.h" +#endif + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + + +local int unz64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int *pie)); + +local int unz64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pie) +{ + unsigned char c; + int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pie = (int)c; + return UNZ_OK; + } + else + { + if (ZERROR64(*pzlib_filefunc_def,filestream)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int unz64local_getShort OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unz64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX) +{ + uLong x ; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unz64local_getLong OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unz64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX) +{ + uLong x ; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unz64local_getLong64 OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + ZPOS64_T *pX)); + + +local int unz64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + ZPOS64_T *pX) +{ + ZPOS64_T x ; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (ZPOS64_T)i; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<8; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<16; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<24; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<32; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<40; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<48; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<56; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +/* My own strcmpi / strcasecmp */ +local int strcmpcasenosensitive_internal (const char* fileName1, const char* fileName2) +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int ZEXPORT unzStringFileNameCompare (const char* fileName1, + const char* fileName2, + int iCaseSensitivity) + +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local ZPOS64_T unz64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); +local ZPOS64_T unz64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + + +/* + Locate the Central directory 64 of a zipfile (at the end, just before + the global comment) +*/ +local ZPOS64_T unz64local_SearchCentralDir64 OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream)); + +local ZPOS64_T unz64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + uLong uL; + ZPOS64_T relativeOffset; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + if (uPosFound == 0) + return 0; + + /* Zip64 end of central directory locator */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature, already checked */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + + /* number of the disk with the start of the zip64 end of central directory */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + if (uL != 0) + return 0; + + /* relative offset of the zip64 end of central directory record */ + if (unz64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=UNZ_OK) + return 0; + + /* total number of disks */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + if (uL != 1) + return 0; + + /* Goto end of central directory record */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + + if (uL != 0x06064b50) + return 0; + + return relativeOffset; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer + "zlib/zlib114.zip". + If the zipfile cannot be opened (file doesn't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +local unzFile unzOpenInternal (const void *path, + zlib_filefunc64_32_def* pzlib_filefunc64_32_def, + int is64bitOpenFunction) +{ + unz64_s us; + unz64_s *s; + ZPOS64_T central_pos; + uLong uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + ZPOS64_T number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + if (unz_copyright[0]!=' ') + return NULL; + + us.z_filefunc.zseek32_file = NULL; + us.z_filefunc.ztell32_file = NULL; + if (pzlib_filefunc64_32_def==NULL) + fill_fopen64_filefunc(&us.z_filefunc.zfile_func64); + else + us.z_filefunc = *pzlib_filefunc64_32_def; + us.is64bitOpenFunction = is64bitOpenFunction; + + + + us.filestream = ZOPEN64(us.z_filefunc, + path, + ZLIB_FILEFUNC_MODE_READ | + ZLIB_FILEFUNC_MODE_EXISTING); + if (us.filestream==NULL) + return NULL; + + central_pos = unz64local_SearchCentralDir64(&us.z_filefunc,us.filestream); + if (central_pos) + { + uLong uS; + ZPOS64_T uL64; + + us.isZip64 = 1; + + if (ZSEEK64(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* size of zip64 end of central directory record */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&uL64)!=UNZ_OK) + err=UNZ_ERRNO; + + /* version made by */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) + err=UNZ_ERRNO; + + /* version needed to extract */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central directory on this disk */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central directory */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + us.gi.size_comment = 0; + } + else + { + central_pos = unz64local_SearchCentralDir(&us.z_filefunc,us.filestream); + if (central_pos==0) + err=UNZ_ERRNO; + + us.isZip64 = 0; + + if (ZSEEK64(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + us.gi.number_entry = uL; + + /* total number of entries in the central dir */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + number_entry_CD = uL; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + us.size_central_dir = uL; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + us.offset_central_dir = uL; + + /* zipfile comment length */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + } + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + ZCLOSE64(s->z_filefunc, s->filestream); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int ZEXPORT unzGetGlobalInfo64 (unzFile file, unz_global_info64* pglobal_info) +{ + unz64_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + +extern int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info* pglobal_info32) +{ + unz64_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + /* to do : check if number_entry is not truncated */ + pglobal_info32->number_entry = (uLong)s->gi.number_entry; + pglobal_info32->size_comment = s->gi.size_comment; + return UNZ_OK; +} +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +local void unz64local_DosDateToTmuDate (ZPOS64_T ulDosDate, tm_unz* ptm) +{ + ZPOS64_T uDate; + uDate = (ZPOS64_T)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +local int unz64local_GetCurrentFileInfoInternal OF((unzFile file, + unz_file_info64 *pfile_info, + unz_file_info64_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +local int unz64local_GetCurrentFileInfoInternal (unzFile file, + unz_file_info64 *pfile_info, + unz_file_info64_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) +{ + unz64_s* s; + unz_file_info64 file_info; + unz_file_info64_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + uLong uL; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (ZSEEK64(s->z_filefunc, s->filestream, + s->pos_in_central_dir+s->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) + { + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + } + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unz64local_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + file_info.compressed_size = uL; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + file_info.uncompressed_size = uL; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + // relative offset of local header + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + file_info_internal.offset_curfile = uL; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (ZREAD64(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + // Read extrafield + if ((err==UNZ_OK) && (extraField!=NULL)) + { + ZPOS64_T uSizeRead ; + if (file_info.size_file_extraz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (ZREAD64(s->z_filefunc, s->filestream,extraField,(uLong)uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + + lSeek += file_info.size_file_extra - (uLong)uSizeRead; + } + else + lSeek += file_info.size_file_extra; + + + if ((err==UNZ_OK) && (file_info.size_file_extra != 0)) + { + uLong acc = 0; + + // since lSeek now points to after the extra field we need to move back + lSeek -= file_info.size_file_extra; + + if (lSeek!=0) + { + if (ZSEEK64(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + + while(acc < file_info.size_file_extra) + { + uLong headerId; + uLong dataSize; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&headerId) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&dataSize) != UNZ_OK) + err=UNZ_ERRNO; + + /* ZIP64 extra fields */ + if (headerId == 0x0001) + { + uLong uL2; + + if(file_info.uncompressed_size == (ZPOS64_T)(unsigned long)-1) + { + if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + } + + if(file_info.compressed_size == (ZPOS64_T)(unsigned long)-1) + { + if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + } + + if(file_info_internal.offset_curfile == (ZPOS64_T)(unsigned long)-1) + { + /* Relative Header offset */ + if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + } + + if(file_info.disk_num_start == (unsigned long)-1) + { + /* Disk Start Number */ + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL2) != UNZ_OK) + err=UNZ_ERRNO; + } + + } + else + { + if (ZSEEK64(s->z_filefunc, s->filestream,dataSize,ZLIB_FILEFUNC_SEEK_CUR)!=0) + err=UNZ_ERRNO; + } + + acc += 2 + 2 + dataSize; + } + } + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + { +#ifndef __clang_analyzer__ + lSeek=0; +#endif + } + else + err=UNZ_ERRNO; + } + + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (ZREAD64(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; +#ifndef __clang_analyzer__ + lSeek+=file_info.size_file_comment - uSizeRead; +#endif + } +#ifndef __clang_analyzer__ + else + lSeek+=file_info.size_file_comment; +#endif + + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int ZEXPORT unzGetCurrentFileInfo64 (unzFile file, + unz_file_info64 * pfile_info, + char * szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char* szComment, uLong commentBufferSize) +{ + return unz64local_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +extern int ZEXPORT unzGetCurrentFileInfo (unzFile file, + unz_file_info * pfile_info, + char * szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char* szComment, uLong commentBufferSize) +{ + int err; + unz_file_info64 file_info64; + err = unz64local_GetCurrentFileInfoInternal(file,&file_info64,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); + if (err==UNZ_OK) + { + pfile_info->version = file_info64.version; + pfile_info->version_needed = file_info64.version_needed; + pfile_info->flag = file_info64.flag; + pfile_info->compression_method = file_info64.compression_method; + pfile_info->dosDate = file_info64.dosDate; + pfile_info->crc = file_info64.crc; + + pfile_info->size_filename = file_info64.size_filename; + pfile_info->size_file_extra = file_info64.size_file_extra; + pfile_info->size_file_comment = file_info64.size_file_comment; + + pfile_info->disk_num_start = file_info64.disk_num_start; + pfile_info->internal_fa = file_info64.internal_fa; + pfile_info->external_fa = file_info64.external_fa; + + pfile_info->tmu_date = file_info64.tmu_date; + + + pfile_info->compressed_size = (uLong)file_info64.compressed_size; + pfile_info->uncompressed_size = (uLong)file_info64.uncompressed_size; + + } + return err; +} +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int ZEXPORT unzGoToFirstFile (unzFile file) +{ + int err=UNZ_OK; + unz64_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int ZEXPORT unzGoToNextFile (unzFile file) +{ + unz64_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ + unz64_s* s; + int err; + + /* We remember the 'current' position in the file so that we can jump + * back there if we fail. + */ + unz_file_info64 cur_file_infoSaved; + unz_file_info64_internal cur_file_info_internalSaved; + ZPOS64_T num_fileSaved; + ZPOS64_T pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + /* Save the current state */ + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + cur_file_infoSaved = s->cur_file_info; + cur_file_info_internalSaved = s->cur_file_info_internal; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + err = unzGetCurrentFileInfo64(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (err == UNZ_OK) + { + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + } + + /* We failed, so restore the state of the 'current file' to where we + * were. + */ + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + s->cur_file_info = cur_file_infoSaved; + s->cur_file_info_internal = cur_file_info_internalSaved; + return err; +} + + +/* +/////////////////////////////////////////// +// Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) +// I need random access +// +// Further optimization could be realized by adding an ability +// to cache the directory in memory. The goal being a single +// comprehensive file read to put the file I need in a memory. +*/ + +/* +typedef struct unz_file_pos_s +{ + ZPOS64_T pos_in_zip_directory; // offset in file + ZPOS64_T num_of_file; // # of file +} unz_file_pos; +*/ + +extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos* file_pos) +{ + unz64_s* s; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + file_pos->pos_in_zip_directory = s->pos_in_central_dir; + file_pos->num_of_file = s->num_file; + + return UNZ_OK; +} + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos) +{ + unz64_file_pos file_pos64; + int err = unzGetFilePos64(file,&file_pos64); + if (err==UNZ_OK) + { + file_pos->pos_in_zip_directory = (uLong)file_pos64.pos_in_zip_directory; + file_pos->num_of_file = (uLong)file_pos64.num_of_file; + } + return err; +} + +extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos* file_pos) +{ + unz64_s* s; + int err; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + + /* jump to the right spot */ + s->pos_in_central_dir = file_pos->pos_in_zip_directory; + s->num_file = file_pos->num_of_file; + + /* set the current file */ + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + /* return results */ + s->current_file_ok = (err == UNZ_OK); + return err; +} + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos) +{ + unz64_file_pos file_pos64; + if (file_pos == NULL) + return UNZ_PARAMERROR; + + file_pos64.pos_in_zip_directory = file_pos->pos_in_zip_directory; + file_pos64.num_of_file = file_pos->num_of_file; + return unzGoToFilePos64(file,&file_pos64); +} + +/* +// Unzip Helper Functions - should be here? +/////////////////////////////////////////// +*/ + +/* + Read the local header of the current zipfile + Check the coherency of the local header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in local header + (filename and size of extra field data) +*/ +local int unz64local_CheckCurrentFileCoherencyHeader (unz64_s* s, uInt* piSizeVar, + ZPOS64_T * poffset_local_extrafield, + uInt * psize_local_extrafield) +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (ZSEEK64(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + { + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + } + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unz64local_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && +/* #ifdef HAVE_BZIP2 */ + (s->cur_file_info.compression_method!=Z_BZIP2ED) && +/* #endif */ + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int ZEXPORT unzOpenCurrentFile3 (unzFile file, int* method, + int* level, int raw, const char* password) +{ + int err=UNZ_OK; + uInt iSizeVar; + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + ZPOS64_T offset_local_extrafield; /* offset of the local extra field */ + uInt size_local_extrafield; /* size of the local extra field */ +# ifndef NOUNCRYPT + char source[12]; +# else + if (password != NULL) + return UNZ_PARAMERROR; +# endif + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unz64local_CheckCurrentFileCoherencyHeader(s,&iSizeVar, &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip64_read_info_s*)ALLOC(sizeof(file_in_zip64_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + pfile_in_zip_read_info->raw=raw; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if (method!=NULL) + *method = (int)s->cur_file_info.compression_method; + + if (level!=NULL) + { + *level = 6; + switch (s->cur_file_info.flag & 0x06) + { + case 6 : *level = 1; break; + case 4 : *level = 2; break; + case 2 : *level = 9; break; + } + } + + if ((s->cur_file_info.compression_method!=0) && +/* #ifdef HAVE_BZIP2 */ + (s->cur_file_info.compression_method!=Z_BZIP2ED) && +/* #endif */ + (s->cur_file_info.compression_method!=Z_DEFLATED)) + { +#ifndef __clang_analyzer__ + err=UNZ_BADZIPFILE; +#endif + } + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->total_out_64=0; + pfile_in_zip_read_info->compression_method = s->cur_file_info.compression_method; + pfile_in_zip_read_info->filestream=s->filestream; + pfile_in_zip_read_info->z_filefunc=s->z_filefunc; +#ifndef __clang_analyzer__ + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; +#endif + + pfile_in_zip_read_info->stream.total_out = 0; + + if ((s->cur_file_info.compression_method==Z_BZIP2ED) && (!raw)) + { +#ifdef HAVE_BZIP2 + pfile_in_zip_read_info->bstream.bzalloc = (void *(*) (void *, int, int))0; + pfile_in_zip_read_info->bstream.bzfree = (free_func)0; + pfile_in_zip_read_info->bstream.opaque = (voidpf)0; + pfile_in_zip_read_info->bstream.state = (voidpf)0; + + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = (voidpf)0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=BZ2_bzDecompressInit(&pfile_in_zip_read_info->bstream, 0, 0); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=Z_BZIP2ED; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } +#else + pfile_in_zip_read_info->raw=1; +#endif + } + else if ((s->cur_file_info.compression_method==Z_DEFLATED) && (!raw)) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = 0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=Z_DEFLATED; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + s->pfile_in_zip_read = pfile_in_zip_read_info; + s->encrypted = 0; + +# ifndef NOUNCRYPT + if (password != NULL) + { + int i; + s->pcrc_32_tab = (const unsigned long*)get_crc_table(); + init_keys(password,s->keys,s->pcrc_32_tab); + if (ZSEEK64(s->z_filefunc, s->filestream, + s->pfile_in_zip_read->pos_in_zipfile + + s->pfile_in_zip_read->byte_before_the_zipfile, + SEEK_SET)!=0) + return UNZ_INTERNALERROR; + if(ZREAD64(s->z_filefunc, s->filestream,source, 12)<12) + return UNZ_INTERNALERROR; + + for (i = 0; i<12; i++) + zdecode(s->keys,s->pcrc_32_tab,source[i]); + + s->pfile_in_zip_read->pos_in_zipfile+=12; + s->encrypted=1; + } +# endif + + + return UNZ_OK; +} + +extern int ZEXPORT unzOpenCurrentFile (unzFile file) +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); +} + +extern int ZEXPORT unzOpenCurrentFilePassword (unzFile file, const char* password) +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, password); +} + +extern int ZEXPORT unzOpenCurrentFile2 (unzFile file, int* method, int* level, int raw) +{ + return unzOpenCurrentFile3(file, method, level, raw, NULL); +} + +/** Addition for GDAL : START */ + +extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64( unzFile file) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + s=(unz64_s*)file; + if (file==NULL) + return 0; //UNZ_PARAMERROR; + pfile_in_zip_read_info=s->pfile_in_zip_read; + if (pfile_in_zip_read_info==NULL) + return 0; //UNZ_PARAMERROR; + return pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile; +} + +/** Addition for GDAL : END */ + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int ZEXPORT unzReadCurrentFile (unzFile file, voidp buf, unsigned len) +{ + int err=UNZ_OK; + uInt iRead = 0; + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if (pfile_in_zip_read_info->read_buffer == NULL) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + // NOTE: + // This bit of code seems to try to set the amount of space in the output buffer based on the + // value stored in the headers stored in the .zip file. However, if those values are incorrect + // it may result in a loss of data when uncompresssing that file. The compressed data is still + // legit and will deflate without knowing the uncompressed code so this tidbit is unnecessary and + // may cause issues for some .zip files. + // + // It's removed in here to fix those issues. + // + // See: https://github.com/samsoffes/ssziparchive/issues/16 + // + + /* + if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && + (!(pfile_in_zip_read_info->raw))) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + */ + + if ((len>pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in) && + (pfile_in_zip_read_info->raw)) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + if (ZREAD64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->read_buffer, + uReadThis)!=uReadThis) + return UNZ_ERRNO; + + +# ifndef NOUNCRYPT + if(s->encrypted) + { + uInt i; + for(i=0;iread_buffer[i] = + zdecode(s->keys,s->pcrc_32_tab, + pfile_in_zip_read_info->read_buffer[i]); + } +# endif + + + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Bytef*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) + { + uInt uDoCopy,i ; + + if ((pfile_in_zip_read_info->stream.avail_in == 0) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + return (iRead==0) ? UNZ_EOF : iRead; + + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uDoCopy; + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else if (pfile_in_zip_read_info->compression_method==Z_BZIP2ED) + { +#ifdef HAVE_BZIP2 + uLong uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + uLong uOutThis; + + pfile_in_zip_read_info->bstream.next_in = (char*)pfile_in_zip_read_info->stream.next_in; + pfile_in_zip_read_info->bstream.avail_in = pfile_in_zip_read_info->stream.avail_in; + pfile_in_zip_read_info->bstream.total_in_lo32 = pfile_in_zip_read_info->stream.total_in; + pfile_in_zip_read_info->bstream.total_in_hi32 = 0; + pfile_in_zip_read_info->bstream.next_out = (char*)pfile_in_zip_read_info->stream.next_out; + pfile_in_zip_read_info->bstream.avail_out = pfile_in_zip_read_info->stream.avail_out; + pfile_in_zip_read_info->bstream.total_out_lo32 = pfile_in_zip_read_info->stream.total_out; + pfile_in_zip_read_info->bstream.total_out_hi32 = 0; + + uTotalOutBefore = pfile_in_zip_read_info->bstream.total_out_lo32; + bufBefore = (const Bytef *)pfile_in_zip_read_info->bstream.next_out; + + err=BZ2_bzDecompress(&pfile_in_zip_read_info->bstream); + + uTotalOutAfter = pfile_in_zip_read_info->bstream.total_out_lo32; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis)); + pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + pfile_in_zip_read_info->stream.next_in = (Bytef*)pfile_in_zip_read_info->bstream.next_in; + pfile_in_zip_read_info->stream.avail_in = pfile_in_zip_read_info->bstream.avail_in; + pfile_in_zip_read_info->stream.total_in = pfile_in_zip_read_info->bstream.total_in_lo32; + pfile_in_zip_read_info->stream.next_out = (Bytef*)pfile_in_zip_read_info->bstream.next_out; + pfile_in_zip_read_info->stream.avail_out = pfile_in_zip_read_info->bstream.avail_out; + pfile_in_zip_read_info->stream.total_out = pfile_in_zip_read_info->bstream.total_out_lo32; + + if (err==BZ_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=BZ_OK) + break; +#endif + } // end Z_BZIP2ED + else + { + ZPOS64_T uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + ZPOS64_T uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) + err = Z_DATA_ERROR; + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern z_off_t ZEXPORT unztell (unzFile file) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (z_off_t)pfile_in_zip_read_info->stream.total_out; +} + +extern ZPOS64_T ZEXPORT unztell64 (unzFile file) +{ + + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return (ZPOS64_T)-1; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return (ZPOS64_T)-1; + + return pfile_in_zip_read_info->total_out_64; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int ZEXPORT unzeof (unzFile file) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* +Read extra field from the current file (opened by unzOpenCurrentFile) +This is the local-header version of the extra field (sometimes, there is +more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int ZEXPORT unzGetLocalExtrafield (unzFile file, voidp buf, unsigned len) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + uInt read_now; + ZPOS64_T size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (ZREAD64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + buf,read_now)!=read_now) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int ZEXPORT unzCloseCurrentFile (unzFile file) +{ + int err=UNZ_OK; + + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && + (!pfile_in_zip_read_info->raw)) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised == Z_DEFLATED) + inflateEnd(&pfile_in_zip_read_info->stream); +#ifdef HAVE_BZIP2 + else if (pfile_in_zip_read_info->stream_initialised == Z_BZIP2ED) + BZ2_bzDecompressEnd(&pfile_in_zip_read_info->bstream); +#endif + + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int ZEXPORT unzGetGlobalComment (unzFile file, char * szComment, uLong uSizeBuf) +{ + unz64_s* s; + uLong uReadThis ; + if (file==NULL) + return (int)UNZ_PARAMERROR; + s=(unz64_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (ZSEEK64(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (ZREAD64(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* Additions by RX '2004 */ +extern ZPOS64_T ZEXPORT unzGetOffset64(unzFile file) +{ + unz64_s* s; + + if (file==NULL) + return 0; //UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return 0; + if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) + if (s->num_file==s->gi.number_entry) + return 0; + return s->pos_in_central_dir; +} + +extern uLong ZEXPORT unzGetOffset (unzFile file) +{ + ZPOS64_T offset64; + + if (file==NULL) + return 0; //UNZ_PARAMERROR; + offset64 = unzGetOffset64(file); + return (uLong)offset64; +} + +extern int ZEXPORT unzSetOffset64(unzFile file, ZPOS64_T pos) +{ + unz64_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + + s->pos_in_central_dir = pos; + s->num_file = s->gi.number_entry; /* hack */ + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos) +{ + return unzSetOffset64(file,pos); +} diff --git a/Source/3rdParty/SSZipArchive/minizip/zip.h b/Source/3rdParty/SSZipArchive/minizip/zip.h new file mode 100755 index 000000000..eec1082b3 --- /dev/null +++ b/Source/3rdParty/SSZipArchive/minizip/zip.h @@ -0,0 +1,362 @@ +/* zip.h -- IO on .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + --------------------------------------------------------------------------- + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + --------------------------------------------------------------------------- + + Changes + + See header of zip.h + +*/ + +#ifndef _zip12_H +#define _zip12_H + +#ifdef __cplusplus +extern "C" { +#endif + +//#define HAVE_BZIP2 + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#ifdef HAVE_BZIP2 +#include "bzlib.h" +#endif + +#define Z_BZIP2ED 12 + +#if defined(STRICTZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagzipFile__ { int unused; } zipFile__; +typedef zipFile__ *zipFile; +#else +typedef voidp zipFile; +#endif + +#define ZIP_OK (0) +#define ZIP_EOF (0) +#define ZIP_ERRNO (Z_ERRNO) +#define ZIP_PARAMERROR (-102) +#define ZIP_BADZIPFILE (-103) +#define ZIP_INTERNALERROR (-104) + +#ifndef DEF_MEM_LEVEL +# if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +# else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +# endif +#endif +/* default memLevel */ + +/* tm_zip contain date/time info */ +typedef struct tm_zip_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_zip; + +typedef struct +{ + tm_zip tmz_date; /* date in understandable format */ + uLong dosDate; /* if dos_date == 0, tmu_date is used */ +/* uLong flag; */ /* general purpose bit flag 2 bytes */ + + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ +} zip_fileinfo; + +typedef const char* zipcharpc; + + +#define APPEND_STATUS_CREATE (0) +#define APPEND_STATUS_CREATEAFTER (1) +#define APPEND_STATUS_ADDINZIP (2) + +extern zipFile ZEXPORT zipOpen OF((const char *pathname, int append)); +extern zipFile ZEXPORT zipOpen64 OF((const void *pathname, int append)); +/* + Create a zipfile. + pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on + an Unix computer "zlib/zlib113.zip". + if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip + will be created at the end of the file. + (useful if the file contain a self extractor code) + if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will + add files in existing zip (be sure you don't add file that doesn't exist) + If the zipfile cannot be opened, the return value is NULL. + Else, the return value is a zipFile Handle, usable with other function + of this zip package. +*/ + +/* Note : there is no delete function into a zipfile. + If you want delete file into a zipfile, you must open a zipfile, and create another + Of couse, you can use RAW reading and writing to copy the file you did not want delte +*/ + +extern zipFile ZEXPORT zipOpen2 OF((const char *pathname, + int append, + zipcharpc* globalcomment, + zlib_filefunc_def* pzlib_filefunc_def)); + +extern zipFile ZEXPORT zipOpen2_64 OF((const void *pathname, + int append, + zipcharpc* globalcomment, + zlib_filefunc64_def* pzlib_filefunc_def)); + +extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level)); + +extern int ZEXPORT zipOpenNewFileInZip64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int zip64)); + +/* + Open a file in the ZIP for writing. + filename : the filename in zip (if NULL, '-' without quote will be used + *zipfi contain supplemental information + if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local + contains the extrafield data the the local header + if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global + contains the extrafield data the the local header + if comment != NULL, comment contain the comment string + method contain the compression method (0 for store, Z_DEFLATED for deflate) + level contain the level of compression (can be Z_DEFAULT_COMPRESSION) + zip64 is set to 1 if a zip64 extended information block should be added to the local file header. + this MUST be '1' if the uncompressed size is >= 0xffffffff. + +*/ + + +extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw)); + + +extern int ZEXPORT zipOpenNewFileInZip2_64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int zip64)); +/* + Same than zipOpenNewFileInZip, except if raw=1, we write raw file + */ + +extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting)); + +extern int ZEXPORT zipOpenNewFileInZip3_64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting, + int zip64 + )); + +/* + Same than zipOpenNewFileInZip2, except + windowBits,memLevel,,strategy : see parameter strategy in deflateInit2 + password : crypting password (NULL for no crypting) + crcForCrypting : crc of file to compress (needed for crypting) + */ + +extern int ZEXPORT zipOpenNewFileInZip4 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting, + uLong versionMadeBy, + uLong flagBase + )); + + +extern int ZEXPORT zipOpenNewFileInZip4_64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting, + uLong versionMadeBy, + uLong flagBase, + int zip64 + )); +/* + Same than zipOpenNewFileInZip4, except + versionMadeBy : value for Version made by field + flag : value for flag field (compression level info will be added) + */ + + +extern int ZEXPORT zipWriteInFileInZip OF((zipFile file, + const void* buf, + unsigned len)); +/* + Write data in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZip OF((zipFile file)); +/* + Close the current file in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file, + uLong uncompressed_size, + uLong crc32)); + +extern int ZEXPORT zipCloseFileInZipRaw64 OF((zipFile file, + ZPOS64_T uncompressed_size, + uLong crc32)); + +/* + Close the current file in the zipfile, for file opened with + parameter raw=1 in zipOpenNewFileInZip2 + uncompressed_size and crc32 are value for the uncompressed size +*/ + +extern int ZEXPORT zipClose OF((zipFile file, + const char* global_comment)); +/* + Close the zipfile +*/ + + +extern int ZEXPORT zipRemoveExtraInfoBlock OF((char* pData, int* dataLen, short sHeader)); +/* + zipRemoveExtraInfoBlock - Added by Mathias Svensson + + Remove extra information block from a extra information data for the local file header or central directory header + + It is needed to remove ZIP64 extra information blocks when before data is written if using RAW mode. + + 0x0001 is the signature header for the ZIP64 extra information blocks + + usage. + Remove ZIP64 Extra information from a central director extra field data + zipRemoveExtraInfoBlock(pCenDirExtraFieldData, &nCenDirExtraFieldDataLen, 0x0001); + + Remove ZIP64 Extra information from a Local File Header extra field data + zipRemoveExtraInfoBlock(pLocalHeaderExtraFieldData, &nLocalHeaderExtraFieldDataLen, 0x0001); +*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _zip64_H */ diff --git a/Source/3rdParty/SSZipArchive/minizip/zip.m b/Source/3rdParty/SSZipArchive/minizip/zip.m new file mode 100755 index 000000000..b3b39169f --- /dev/null +++ b/Source/3rdParty/SSZipArchive/minizip/zip.m @@ -0,0 +1,2023 @@ +/* zip.c -- IO on .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + Changes + Oct-2009 - Mathias Svensson - Remove old C style function prototypes + Oct-2009 - Mathias Svensson - Added Zip64 Support when creating new file archives + Oct-2009 - Mathias Svensson - Did some code cleanup and refactoring to get better overview of some functions. + Oct-2009 - Mathias Svensson - Added zipRemoveExtraInfoBlock to strip extra field data from its ZIP64 data + It is used when recreting zip archive with RAW when deleting items from a zip. + ZIP64 data is automaticly added to items that needs it, and existing ZIP64 data need to be removed. + Oct-2009 - Mathias Svensson - Added support for BZIP2 as compression mode (bzip2 lib is required) + Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer + +*/ + + +#include +#include +#include +#include +#include "zlib.h" +#include "zip.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + +extern int errno; +#else +# include +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +#ifndef VERSIONMADEBY +# define VERSIONMADEBY (0x0) /* platform depedent */ +#endif + +#ifndef Z_BUFSIZE +#define Z_BUFSIZE (64*1024) //(16384) +#endif + +#ifndef Z_MAXFILENAMEINZIP +#define Z_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +/* +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) +*/ + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + + +// NOT sure that this work on ALL platform +#define MAKEULONG64(a, b) ((ZPOS64_T)(((unsigned long)(a)) | ((ZPOS64_T)((unsigned long)(b))) << 32)) + +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +#ifndef DEF_MEM_LEVEL +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +#endif +const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + + +#define SIZEDATA_INDATABLOCK (4096-(4*4)) + +#define LOCALHEADERMAGIC (0x04034b50) +#define CENTRALHEADERMAGIC (0x02014b50) +#define ENDHEADERMAGIC (0x06054b50) +#define ZIP64ENDHEADERMAGIC (0x6064b50) +#define ZIP64ENDLOCHEADERMAGIC (0x7064b50) + +#define FLAG_LOCALHEADER_OFFSET (0x06) +#define CRC_LOCALHEADER_OFFSET (0x0e) + +#define SIZECENTRALHEADER (0x2e) /* 46 */ + +typedef struct linkedlist_datablock_internal_s +{ + struct linkedlist_datablock_internal_s* next_datablock; + uLong avail_in_this_block; + uLong filled_in_this_block; + uLong unused; /* for future use and alignement */ + unsigned char data[SIZEDATA_INDATABLOCK]; +} linkedlist_datablock_internal; + +typedef struct linkedlist_data_s +{ + linkedlist_datablock_internal* first_block; + linkedlist_datablock_internal* last_block; +} linkedlist_data; + + +typedef struct +{ + z_stream stream; /* zLib stream structure for inflate */ +#ifdef HAVE_BZIP2 + bz_stream bstream; /* bzLib stream structure for bziped */ +#endif + + int stream_initialised; /* 1 is stream is initialised */ + uInt pos_in_buffered_data; /* last written byte in buffered_data */ + + ZPOS64_T pos_local_header; /* offset of the local header of the file + currenty writing */ + char* central_header; /* central header data for the current file */ + uLong size_centralExtra; + uLong size_centralheader; /* size of the central header for cur file */ + uLong size_centralExtraFree; /* Extra bytes allocated to the centralheader but that are not used */ + uLong flag; /* flag of the file currently writing */ + + int method; /* compression method of file currenty wr.*/ + int raw; /* 1 for directly writing raw data */ + Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/ + uLong dosDate; + uLong crc32; + int encrypt; + int zip64; /* Add ZIP64 extened information in the extra field */ + ZPOS64_T pos_zip64extrainfo; + ZPOS64_T totalCompressedData; + ZPOS64_T totalUncompressedData; +#ifndef NOCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned long* pcrc_32_tab; + int crypt_header_size; +#endif +} curfile64_info; + +typedef struct +{ + zlib_filefunc64_32_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + linkedlist_data central_dir;/* datablock with central dir in construction*/ + int in_opened_file_inzip; /* 1 if a file in the zip is currently writ.*/ + curfile64_info ci; /* info on the file curretly writing */ + + ZPOS64_T begin_pos; /* position of the beginning of the zipfile */ + ZPOS64_T add_position_when_writting_offset; + ZPOS64_T number_entry; + +#ifndef NO_ADDFILEINEXISTINGZIP + char *globalcomment; +#endif + +} zip64_internal; + + +#ifndef NOCRYPT +#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED +#include "crypt.h" +#endif + +local linkedlist_datablock_internal* allocate_new_datablock() +{ + linkedlist_datablock_internal* ldi; + ldi = (linkedlist_datablock_internal*) + ALLOC(sizeof(linkedlist_datablock_internal)); + if (ldi!=NULL) + { + ldi->next_datablock = NULL ; + ldi->filled_in_this_block = 0 ; + ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ; + } + return ldi; +} + +local void free_datablock(linkedlist_datablock_internal* ldi) +{ + while (ldi!=NULL) + { + linkedlist_datablock_internal* ldinext = ldi->next_datablock; + TRYFREE(ldi); + ldi = ldinext; + } +} + +local void init_linkedlist(linkedlist_data* ll) +{ + ll->first_block = ll->last_block = NULL; +} + +local void free_linkedlist(linkedlist_data* ll) +{ + free_datablock(ll->first_block); + ll->first_block = ll->last_block = NULL; +} + + +local int add_data_in_datablock(linkedlist_data* ll, const void* buf, uLong len) +{ + linkedlist_datablock_internal* ldi; + const unsigned char* from_copy; + + if (ll==NULL) + return ZIP_INTERNALERROR; + + if (ll->last_block == NULL) + { + ll->first_block = ll->last_block = allocate_new_datablock(); + if (ll->first_block == NULL) + return ZIP_INTERNALERROR; + } + + ldi = ll->last_block; + from_copy = (unsigned char*)buf; + + while (len>0) + { + uInt copy_this; + uInt i; + unsigned char* to_copy; + + if (ldi->avail_in_this_block==0) + { + ldi->next_datablock = allocate_new_datablock(); + if (ldi->next_datablock == NULL) + return ZIP_INTERNALERROR; + ldi = ldi->next_datablock ; + ll->last_block = ldi; + } + + if (ldi->avail_in_this_block < len) + copy_this = (uInt)ldi->avail_in_this_block; + else + copy_this = (uInt)len; + + to_copy = &(ldi->data[ldi->filled_in_this_block]); + + for (i=0;ifilled_in_this_block += copy_this; + ldi->avail_in_this_block -= copy_this; + from_copy += copy_this ; + len -= copy_this; + } + return ZIP_OK; +} + + + +/****************************************************************************/ + +#ifndef NO_ADDFILEINEXISTINGZIP +/* =========================================================================== + Inputs a long in LSB order to the given file + nbByte == 1, 2 ,4 or 8 (byte, short or long, ZPOS64_T) +*/ + +local int zip64local_putValue OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte)); +local int zip64local_putValue (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte) +{ + unsigned char buf[8]; + int n; + for (n = 0; n < nbByte; n++) + { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + if (x != 0) + { /* data overflow - hack for ZIP64 (X Roche) */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } + + if (ZWRITE64(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte) + return ZIP_ERRNO; + else + return ZIP_OK; +} + +local void zip64local_putValue_inmemory OF((void* dest, ZPOS64_T x, int nbByte)); +local void zip64local_putValue_inmemory (void* dest, ZPOS64_T x, int nbByte) +{ + unsigned char* buf=(unsigned char*)dest; + int n; + for (n = 0; n < nbByte; n++) { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + + if (x != 0) + { /* data overflow - hack for ZIP64 */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } +} + +/****************************************************************************/ + + +local uLong zip64local_TmzDateToDosDate(const tm_zip* ptm) +{ + uLong year = (uLong)ptm->tm_year; + if (year>=1980) + year-=1980; + else if (year>=80) + year-=80; + return + (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) | + ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour)); +} + + +/****************************************************************************/ + +local int zip64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pye)); + +local int zip64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int* pye) +{ + unsigned char c; + int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pye = (int)c; + return ZIP_OK; + } + else + { + if (ZERROR64(*pzlib_filefunc_def,filestream)) + return ZIP_ERRNO; + else + return ZIP_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int zip64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); + +local int zip64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) +{ + uLong x ; + int i = 0; + int err; + + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int zip64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); + +local int zip64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) +{ + uLong x ; + int i = 0; + int err; + + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<16; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int zip64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)); + + +local int zip64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX) +{ + ZPOS64_T x; + int i = 0; + int err; + + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (ZPOS64_T)i; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<8; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<16; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<24; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<32; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<40; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<48; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<56; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + + return err; +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local ZPOS64_T zip64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); + +local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +/* +Locate the End of Zip64 Central directory locator and from there find the CD of a zipfile (at the end, just before +the global comment) +*/ +local ZPOS64_T zip64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); + +local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + uLong uL; + ZPOS64_T relativeOffset; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + { + // Signature "0x07064b50" Zip64 end of central directory locater + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) + { + uPosFound = uReadPos+i; + break; + } + } + + if (uPosFound!=0) + break; + } + + TRYFREE(buf); + if (uPosFound == 0) + return 0; + + /* Zip64 end of central directory locator */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature, already checked */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + + /* number of the disk with the start of the zip64 end of central directory */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + if (uL != 0) + return 0; + + /* relative offset of the zip64 end of central directory record */ + if (zip64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=ZIP_OK) + return 0; + + /* total number of disks */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + if (uL != 1) + return 0; + + /* Goto Zip64 end of central directory record */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + + if (uL != 0x06064b50) // signature of 'Zip64 end of central directory' + return 0; + + return relativeOffset; +} + +int LoadCentralDirectoryRecord(zip64_internal* pziinit); +int LoadCentralDirectoryRecord(zip64_internal* pziinit) +{ + int err=ZIP_OK; + ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + + ZPOS64_T size_central_dir; /* size of the central directory */ + ZPOS64_T offset_central_dir; /* offset of start of central directory */ + ZPOS64_T central_pos; + uLong uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + ZPOS64_T number_entry; + ZPOS64_T number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + uLong VersionMadeBy; + uLong VersionNeeded; + uLong size_comment; + + int hasZIP64Record = 0; + + // check first if we find a ZIP64 record + central_pos = zip64local_SearchCentralDir64(&pziinit->z_filefunc,pziinit->filestream); + if(central_pos > 0) + { + hasZIP64Record = 1; + } + else if(central_pos == 0) + { + central_pos = zip64local_SearchCentralDir(&pziinit->z_filefunc,pziinit->filestream); + } + +/* disable to allow appending to empty ZIP archive + if (central_pos==0) + err=ZIP_ERRNO; +*/ + + if(hasZIP64Record) + { + ZPOS64_T sizeEndOfCentralDirectory; + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + /* the signature, already checked */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) + err=ZIP_ERRNO; + + /* size of zip64 end of central directory record */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &sizeEndOfCentralDirectory)!=ZIP_OK) + err=ZIP_ERRNO; + + /* version made by */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionMadeBy)!=ZIP_OK) + err=ZIP_ERRNO; + + /* version needed to extract */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionNeeded)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of this disk */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of the disk with the start of the central directory */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central directory on this disk */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &number_entry)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central directory */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&number_entry_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) + err=ZIP_BADZIPFILE; + + /* size of the central directory */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&size_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&offset_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + // TODO.. + // read the comment from the standard central header. + size_comment = 0; + } + else + { + // Read End of central Directory info + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=ZIP_ERRNO; + + /* the signature, already checked */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of this disk */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of the disk with the start of the central directory */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central dir on this disk */ + number_entry = 0; + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + number_entry = uL; + + /* total number of entries in the central dir */ + number_entry_CD = 0; + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + number_entry_CD = uL; + + if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) + err=ZIP_BADZIPFILE; + + /* size of the central directory */ + size_central_dir = 0; + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + size_central_dir = uL; + + /* offset of start of central directory with respect to the starting disk number */ + offset_central_dir = 0; + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + offset_central_dir = uL; + + + /* zipfile global comment length */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &size_comment)!=ZIP_OK) + err=ZIP_ERRNO; + } + + if ((central_posz_filefunc, pziinit->filestream); + return ZIP_ERRNO; + } + + if (size_comment>0) + { + pziinit->globalcomment = (char*)ALLOC(size_comment+1); + if (pziinit->globalcomment) + { + size_comment = ZREAD64(pziinit->z_filefunc, pziinit->filestream, pziinit->globalcomment,size_comment); + pziinit->globalcomment[size_comment]=0; + } + } + + byte_before_the_zipfile = central_pos - (offset_central_dir+size_central_dir); + pziinit->add_position_when_writting_offset = byte_before_the_zipfile; + + { + ZPOS64_T size_central_dir_to_read = size_central_dir; + size_t buf_size = SIZEDATA_INDATABLOCK; + void* buf_read = (void*)ALLOC(buf_size); + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir + byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + while ((size_central_dir_to_read>0) && (err==ZIP_OK)) + { + ZPOS64_T read_this = SIZEDATA_INDATABLOCK; + if (read_this > size_central_dir_to_read) + read_this = size_central_dir_to_read; + + if (ZREAD64(pziinit->z_filefunc, pziinit->filestream,buf_read,(uLong)read_this) != read_this) + err=ZIP_ERRNO; + + if (err==ZIP_OK) + err = add_data_in_datablock(&pziinit->central_dir,buf_read, (uLong)read_this); + + size_central_dir_to_read-=read_this; + } + TRYFREE(buf_read); + } + pziinit->begin_pos = byte_before_the_zipfile; + pziinit->number_entry = number_entry_CD; + + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + return err; +} + + +#endif /* !NO_ADDFILEINEXISTINGZIP*/ + + +/************************************************************/ +extern zipFile ZEXPORT zipOpen3 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def); +extern zipFile ZEXPORT zipOpen3 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def) +{ + zip64_internal ziinit; + zip64_internal* zi; + int err=ZIP_OK; + + ziinit.z_filefunc.zseek32_file = NULL; + ziinit.z_filefunc.ztell32_file = NULL; + if (pzlib_filefunc64_32_def==NULL) + fill_fopen64_filefunc(&ziinit.z_filefunc.zfile_func64); + else + ziinit.z_filefunc = *pzlib_filefunc64_32_def; + + ziinit.filestream = ZOPEN64(ziinit.z_filefunc, + pathname, + (append == APPEND_STATUS_CREATE) ? + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) : + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING)); + + if (ziinit.filestream == NULL) + return NULL; + + if (append == APPEND_STATUS_CREATEAFTER) + ZSEEK64(ziinit.z_filefunc,ziinit.filestream,0,SEEK_END); + + ziinit.begin_pos = ZTELL64(ziinit.z_filefunc,ziinit.filestream); + ziinit.in_opened_file_inzip = 0; + ziinit.ci.stream_initialised = 0; + ziinit.number_entry = 0; + ziinit.add_position_when_writting_offset = 0; + init_linkedlist(&(ziinit.central_dir)); + + + + zi = (zip64_internal*)ALLOC(sizeof(zip64_internal)); + if (zi==NULL) + { + ZCLOSE64(ziinit.z_filefunc,ziinit.filestream); + return NULL; + } + + /* now we add file in a zipfile */ +# ifndef NO_ADDFILEINEXISTINGZIP + ziinit.globalcomment = NULL; + if (append == APPEND_STATUS_ADDINZIP) + { + // Read and Cache Central Directory Records + err = LoadCentralDirectoryRecord(&ziinit); + } + + if (globalcomment) + { + *globalcomment = ziinit.globalcomment; + } +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + + if (err != ZIP_OK) + { +# ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(ziinit.globalcomment); +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + TRYFREE(zi); + return NULL; + } + else + { + *zi = ziinit; + return (zipFile)zi; + } +} + +extern zipFile ZEXPORT zipOpen2 (const char *pathname, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc32_def) +{ + if (pzlib_filefunc32_def != NULL) + { + zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; + fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def); + return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill); + } + else + return zipOpen3(pathname, append, globalcomment, NULL); +} + +extern zipFile ZEXPORT zipOpen2_64 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def) +{ + if (pzlib_filefunc_def != NULL) + { + zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; + zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def; + zlib_filefunc64_32_def_fill.ztell32_file = NULL; + zlib_filefunc64_32_def_fill.zseek32_file = NULL; + return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill); + } + else + return zipOpen3(pathname, append, globalcomment, NULL); +} + + + +extern zipFile ZEXPORT zipOpen (const char* pathname, int append) +{ + return zipOpen3((const void*)pathname,append,NULL,NULL); +} + +extern zipFile ZEXPORT zipOpen64 (const void* pathname, int append) +{ + return zipOpen3(pathname,append,NULL,NULL); +} + +int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local); +int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local) +{ + /* write the local header */ + int err; + uInt size_filename = (uInt)strlen(filename); + uInt size_extrafield = size_extrafield_local; + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC, 4); + + if (err==ZIP_OK) + { + if(zi->ci.zip64) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);/* version needed to extract */ + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)20,2);/* version needed to extract */ + } + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2); + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2); + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4); + + // CRC / Compressed size / Uncompressed size will be filled in later and rewritten later + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */ + if (err==ZIP_OK) + { + if(zi->ci.zip64) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* compressed size, unknown */ + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */ + } + if (err==ZIP_OK) + { + if(zi->ci.zip64) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* uncompressed size, unknown */ + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */ + } + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2); + + if(zi->ci.zip64) + { + size_extrafield += 20; + } + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield,2); + + if ((err==ZIP_OK) && (size_filename > 0)) + { + if (ZWRITE64(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename) + err = ZIP_ERRNO; + } + + if ((err==ZIP_OK) && (size_extrafield_local > 0)) + { + if (ZWRITE64(zi->z_filefunc, zi->filestream, extrafield_local, size_extrafield_local) != size_extrafield_local) + err = ZIP_ERRNO; + } + + + if ((err==ZIP_OK) && (zi->ci.zip64)) + { + // write the Zip64 extended info + short HeaderID = 1; + short DataSize = 16; + ZPOS64_T CompressedSize = 0; + ZPOS64_T UncompressedSize = 0; + + // Remember position of Zip64 extended info for the local file header. (needed when we update size after done with file) + zi->ci.pos_zip64extrainfo = ZTELL64(zi->z_filefunc,zi->filestream); + +#ifndef __clang_analyzer__ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)HeaderID,2); + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)DataSize,2); + + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)UncompressedSize,8); + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)CompressedSize,8); +#endif + } + + return err; +} + +/* + NOTE. + When writing RAW the ZIP64 extended information in extrafield_local and extrafield_global needs to be stripped + before calling this function it can be done with zipRemoveExtraInfoBlock + + It is not done here because then we need to realloc a new buffer since parameters are 'const' and I want to minimize + unnecessary allocations. + */ +extern int ZEXPORT zipOpenNewFileInZip4_64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting, + uLong versionMadeBy, uLong flagBase, int zip64) +{ + zip64_internal* zi; + uInt size_filename; + uInt size_comment; + uInt i; + int err = ZIP_OK; + +# ifdef NOCRYPT + if (password != NULL) + return ZIP_PARAMERROR; +# endif + + if (file == NULL) + return ZIP_PARAMERROR; + +#ifdef HAVE_BZIP2 + if ((method!=0) && (method!=Z_DEFLATED) && (method!=Z_BZIP2ED)) + return ZIP_PARAMERROR; +#else + if ((method!=0) && (method!=Z_DEFLATED)) + return ZIP_PARAMERROR; +#endif + + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + if (err != ZIP_OK) + return err; + } + + if (filename==NULL) + filename="-"; + + if (comment==NULL) + size_comment = 0; + else + size_comment = (uInt)strlen(comment); + + size_filename = (uInt)strlen(filename); + + if (zipfi == NULL) + zi->ci.dosDate = 0; + else + { + if (zipfi->dosDate != 0) + zi->ci.dosDate = zipfi->dosDate; + else + zi->ci.dosDate = zip64local_TmzDateToDosDate(&zipfi->tmz_date); + } + + zi->ci.flag = flagBase; + if (level==8 || level==9) + zi->ci.flag |= 2; + if (level==2) + zi->ci.flag |= 4; + if (level==1) + zi->ci.flag |= 6; + if (password != NULL) + zi->ci.flag |= 1; + + zi->ci.crc32 = 0; + zi->ci.method = method; + zi->ci.encrypt = 0; + zi->ci.stream_initialised = 0; + zi->ci.pos_in_buffered_data = 0; + zi->ci.raw = raw; + zi->ci.pos_local_header = ZTELL64(zi->z_filefunc,zi->filestream); + + zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment; + zi->ci.size_centralExtraFree = 32; // Extra space we have reserved in case we need to add ZIP64 extra info data + + zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree); + + zi->ci.size_centralExtra = size_extrafield_global; + zip64local_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4); + /* version info */ + zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)versionMadeBy,2); + zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)20,2); + zip64local_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2); + zip64local_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2); + zip64local_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4); + zip64local_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/ + zip64local_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/ + zip64local_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/ + zip64local_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2); + zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2); + zip64local_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2); + zip64local_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/ + + if (zipfi==NULL) + zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2); + else + zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2); + + if (zipfi==NULL) + zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4); + else + zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4); + + if(zi->ci.pos_local_header >= 0xffffffff) + zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)0xffffffff,4); + else + zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header - zi->add_position_when_writting_offset,4); + + for (i=0;ici.central_header+SIZECENTRALHEADER+i) = *(filename+i); + + for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+i) = + *(((const char*)extrafield_global)+i); + + for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+ + size_extrafield_global+i) = *(comment+i); + if (zi->ci.central_header == NULL) + return ZIP_INTERNALERROR; + + zi->ci.zip64 = zip64; + zi->ci.totalCompressedData = 0; + zi->ci.totalUncompressedData = 0; + zi->ci.pos_zip64extrainfo = 0; + + err = Write_LocalFileHeader(zi, filename, size_extrafield_local, extrafield_local); + +#ifdef HAVE_BZIP2 + zi->ci.bstream.avail_in = (uInt)0; + zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; + zi->ci.bstream.total_in_hi32 = 0; + zi->ci.bstream.total_in_lo32 = 0; + zi->ci.bstream.total_out_hi32 = 0; + zi->ci.bstream.total_out_lo32 = 0; +#endif + + zi->ci.stream.avail_in = (uInt)0; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + zi->ci.stream.total_in = 0; + zi->ci.stream.total_out = 0; + zi->ci.stream.data_type = Z_BINARY; + +#ifdef HAVE_BZIP2 + if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED || zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) +#else + if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) +#endif + { + if(zi->ci.method == Z_DEFLATED) + { + zi->ci.stream.zalloc = (alloc_func)0; + zi->ci.stream.zfree = (free_func)0; + zi->ci.stream.opaque = (voidpf)0; + + if (windowBits>0) + windowBits = -windowBits; + + err = deflateInit2(&zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy); + + if (err==Z_OK) + zi->ci.stream_initialised = Z_DEFLATED; + } + else if(zi->ci.method == Z_BZIP2ED) + { +#ifdef HAVE_BZIP2 + // Init BZip stuff here + zi->ci.bstream.bzalloc = 0; + zi->ci.bstream.bzfree = 0; + zi->ci.bstream.opaque = (voidpf)0; + + err = BZ2_bzCompressInit(&zi->ci.bstream, level, 0,35); + if(err == BZ_OK) + zi->ci.stream_initialised = Z_BZIP2ED; +#endif + } + + } + +# ifndef NOCRYPT + zi->ci.crypt_header_size = 0; + if ((err==Z_OK) && (password != NULL)) + { + unsigned char bufHead[RAND_HEAD_LEN]; + unsigned int sizeHead; + zi->ci.encrypt = 1; + zi->ci.pcrc_32_tab = (const unsigned long*)get_crc_table(); + /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/ + + sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting); + zi->ci.crypt_header_size = sizeHead; + + if (ZWRITE64(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead) + err = ZIP_ERRNO; + } +# endif + + if (err==Z_OK) + zi->in_opened_file_inzip = 1; + return err; +} + +extern int ZEXPORT zipOpenNewFileInZip4 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting, + uLong versionMadeBy, uLong flagBase) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting, versionMadeBy, flagBase, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip3 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting, VERSIONMADEBY, 0, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting, int zip64) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting, VERSIONMADEBY, 0, zip64); +} + +extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, int zip64) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, zip64); +} + +extern int ZEXPORT zipOpenNewFileInZip64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void*extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int zip64) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, 0, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, zip64); +} + +extern int ZEXPORT zipOpenNewFileInZip (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void*extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, 0, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, 0); +} + +local int zip64FlushWriteBuffer(zip64_internal* zi) +{ + int err=ZIP_OK; + + if (zi->ci.encrypt != 0) + { +#ifndef NOCRYPT + uInt i; + int t; + for (i=0;ici.pos_in_buffered_data;i++) + zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i],t); +#endif + } + + if (ZWRITE64(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) != zi->ci.pos_in_buffered_data) + err = ZIP_ERRNO; + + zi->ci.totalCompressedData += zi->ci.pos_in_buffered_data; + +#ifdef HAVE_BZIP2 + if(zi->ci.method == Z_BZIP2ED) + { + zi->ci.totalUncompressedData += zi->ci.bstream.total_in_lo32; + zi->ci.bstream.total_in_lo32 = 0; + zi->ci.bstream.total_in_hi32 = 0; + } + else +#endif + { + zi->ci.totalUncompressedData += zi->ci.stream.total_in; + zi->ci.stream.total_in = 0; + } + + + zi->ci.pos_in_buffered_data = 0; + + return err; +} + +extern int ZEXPORT zipWriteInFileInZip (zipFile file,const void* buf,unsigned int len) +{ + zip64_internal* zi; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + + zi->ci.crc32 = crc32(zi->ci.crc32,buf,(uInt)len); + +#ifdef HAVE_BZIP2 + if(zi->ci.method == Z_BZIP2ED && (!zi->ci.raw)) + { + zi->ci.bstream.next_in = (void*)buf; + zi->ci.bstream.avail_in = len; + err = BZ_RUN_OK; + + while ((err==BZ_RUN_OK) && (zi->ci.bstream.avail_in>0)) + { + if (zi->ci.bstream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; + } + + + if(err != BZ_RUN_OK) + break; + + if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) + { + uLong uTotalOutBefore_lo = zi->ci.bstream.total_out_lo32; +// uLong uTotalOutBefore_hi = zi->ci.bstream.total_out_hi32; + err=BZ2_bzCompress(&zi->ci.bstream, BZ_RUN); + + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore_lo) ; + } + } + + if(err == BZ_RUN_OK) + err = ZIP_OK; + } + else +#endif + { + zi->ci.stream.next_in = (Bytef*)buf; + zi->ci.stream.avail_in = len; + + while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0)) + { + if (zi->ci.stream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + + + if(err != ZIP_OK) + break; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + uLong uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_NO_FLUSH); + if(uTotalOutBefore > zi->ci.stream.total_out) + { + int bBreak = 0; + bBreak++; + } + + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + } + else + { + uInt copy_this,i; + if (zi->ci.stream.avail_in < zi->ci.stream.avail_out) + copy_this = zi->ci.stream.avail_in; + else + copy_this = zi->ci.stream.avail_out; + + for (i = 0; i < copy_this; i++) + *(((char*)zi->ci.stream.next_out)+i) = + *(((const char*)zi->ci.stream.next_in)+i); + { + zi->ci.stream.avail_in -= copy_this; + zi->ci.stream.avail_out-= copy_this; + zi->ci.stream.next_in+= copy_this; + zi->ci.stream.next_out+= copy_this; + zi->ci.stream.total_in+= copy_this; + zi->ci.stream.total_out+= copy_this; + zi->ci.pos_in_buffered_data += copy_this; + } + } + }// while(...) + } + + return err; +} + +extern int ZEXPORT zipCloseFileInZipRaw (zipFile file, uLong uncompressed_size, uLong crc32) +{ + return zipCloseFileInZipRaw64 (file, uncompressed_size, crc32); +} + +extern int ZEXPORT zipCloseFileInZipRaw64 (zipFile file, ZPOS64_T uncompressed_size, uLong crc32) +{ + zip64_internal* zi; + ZPOS64_T compressed_size; + uLong invalidValue = 0xffffffff; + short datasize = 0; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + zi->ci.stream.avail_in = 0; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + while (err==ZIP_OK) + { + uLong uTotalOutBefore; + if (zi->ci.stream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + { +#ifndef __clang_analyzer__ + err = ZIP_ERRNO; +#endif + } + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; +#ifndef __clang_analyzer__ + zi->ci.stream.next_out = zi->ci.buffered_data; +#endif + } + uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_FINISH); + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + } + } + else if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) + { +#ifdef HAVE_BZIP2 + err = BZ_FINISH_OK; + while (err==BZ_FINISH_OK) + { + uLong uTotalOutBefore; + if (zi->ci.bstream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; + } + uTotalOutBefore = zi->ci.bstream.total_out_lo32; + err=BZ2_bzCompress(&zi->ci.bstream, BZ_FINISH); + if(err == BZ_STREAM_END) + err = Z_STREAM_END; + + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore); + } + + if(err == BZ_FINISH_OK) + err = ZIP_OK; +#endif + } + + if (err==Z_STREAM_END) + err=ZIP_OK; /* this is normal */ + + if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK)) + { + if (zip64FlushWriteBuffer(zi)==ZIP_ERRNO) + err = ZIP_ERRNO; + } + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + int tmp_err = deflateEnd(&zi->ci.stream); + if (err == ZIP_OK) + err = tmp_err; + zi->ci.stream_initialised = 0; + } +#ifdef HAVE_BZIP2 + else if((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) + { + int tmperr = BZ2_bzCompressEnd(&zi->ci.bstream); + if (err==ZIP_OK) + err = tmperr; + zi->ci.stream_initialised = 0; + } +#endif + + if (!zi->ci.raw) + { + crc32 = (uLong)zi->ci.crc32; + uncompressed_size = zi->ci.totalUncompressedData; + } + compressed_size = zi->ci.totalCompressedData; + +# ifndef NOCRYPT + compressed_size += zi->ci.crypt_header_size; +# endif + + // update Current Item crc and sizes, + if(compressed_size >= 0xffffffff || uncompressed_size >= 0xffffffff || zi->ci.pos_local_header >= 0xffffffff) + { + /*version Made by*/ + zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)45,2); + /*version needed*/ + zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)45,2); + + } + + zip64local_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/ + + + if(compressed_size >= 0xffffffff) + zip64local_putValue_inmemory(zi->ci.central_header+20, invalidValue,4); /*compr size*/ + else + zip64local_putValue_inmemory(zi->ci.central_header+20, compressed_size,4); /*compr size*/ + + /// set internal file attributes field + if (zi->ci.stream.data_type == Z_ASCII) + zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2); + + if(uncompressed_size >= 0xffffffff) + zip64local_putValue_inmemory(zi->ci.central_header+24, invalidValue,4); /*uncompr size*/ + else + zip64local_putValue_inmemory(zi->ci.central_header+24, uncompressed_size,4); /*uncompr size*/ + + // Add ZIP64 extra info field for uncompressed size + if(uncompressed_size >= 0xffffffff) + datasize += 8; + + // Add ZIP64 extra info field for compressed size + if(compressed_size >= 0xffffffff) + datasize += 8; + + // Add ZIP64 extra info field for relative offset to local file header of current file + if(zi->ci.pos_local_header >= 0xffffffff) + datasize += 8; + + if(datasize > 0) + { + char* p = NULL; + + if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree) + { + // we can not write more data to the buffer that we have room for. + return ZIP_BADZIPFILE; + } + + p = zi->ci.central_header + zi->ci.size_centralheader; + + // Add Extra Information Header for 'ZIP64 information' + zip64local_putValue_inmemory(p, 0x0001, 2); // HeaderID + p += 2; + zip64local_putValue_inmemory(p, datasize, 2); // DataSize + p += 2; + + if(uncompressed_size >= 0xffffffff) + { + zip64local_putValue_inmemory(p, uncompressed_size, 8); + p += 8; + } + + if(compressed_size >= 0xffffffff) + { + zip64local_putValue_inmemory(p, compressed_size, 8); + p += 8; + } + + if(zi->ci.pos_local_header >= 0xffffffff) + { + zip64local_putValue_inmemory(p, zi->ci.pos_local_header, 8); +#ifndef __clang_analyzer__ + p += 8; +#endif + } + + // Update how much extra free space we got in the memory buffer + // and increase the centralheader size so the new ZIP64 fields are included + // ( 4 below is the size of HeaderID and DataSize field ) + zi->ci.size_centralExtraFree -= datasize + 4; + zi->ci.size_centralheader += datasize + 4; + + // Update the extra info size field + zi->ci.size_centralExtra += datasize + 4; + zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)zi->ci.size_centralExtra,2); + } + + if (err==ZIP_OK) + err = add_data_in_datablock(&zi->central_dir, zi->ci.central_header, (uLong)zi->ci.size_centralheader); + + free(zi->ci.central_header); + + if (err==ZIP_OK) + { + // Update the LocalFileHeader with the new values. + + ZPOS64_T cur_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); + + if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */ + + if(uncompressed_size >= 0xffffffff) + { + if(zi->ci.pos_zip64extrainfo > 0) + { + // Update the size in the ZIP64 extended field. + if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_zip64extrainfo + 4,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + + if (err==ZIP_OK) /* compressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, uncompressed_size, 8); + + if (err==ZIP_OK) /* uncompressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, compressed_size, 8); + } + } + else + { + if (err==ZIP_OK) /* compressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4); + + if (err==ZIP_OK) /* uncompressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4); + } + + if (ZSEEK64(zi->z_filefunc,zi->filestream, cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + } + + zi->number_entry ++; + zi->in_opened_file_inzip = 0; + + return err; +} + +extern int ZEXPORT zipCloseFileInZip (zipFile file) +{ + return zipCloseFileInZipRaw (file,0,0); +} + +int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip); +int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip) +{ + int err = ZIP_OK; + ZPOS64_T pos = zip64eocd_pos_inzip - zi->add_position_when_writting_offset; + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDLOCHEADERMAGIC,4); + + /*num disks*/ + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); + + /*relative offset*/ + if (err==ZIP_OK) /* Relative offset to the Zip64EndOfCentralDirectory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, pos,8); + + /*total disks*/ /* Do not support spawning of disk so always say 1 here*/ + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)1,4); + + return err; +} + +int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip); +int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) +{ + int err = ZIP_OK; + + uLong Zip64DataSize = 44; + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDHEADERMAGIC,4); + + if (err==ZIP_OK) /* size of this 'zip64 end of central directory' */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)Zip64DataSize,8); // why ZPOS64_T of this ? + + if (err==ZIP_OK) /* version made by */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); + + if (err==ZIP_OK) /* version needed */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); + + if (err==ZIP_OK) /* number of this disk */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); + + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); + + if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); + + if (err==ZIP_OK) /* total number of entries in the central dir */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); + + if (err==ZIP_OK) /* size of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)size_centraldir,8); + + if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ + { + ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (ZPOS64_T)pos,8); + } + return err; +} + +int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip); +int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) +{ + int err = ZIP_OK; + + /*signature*/ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4); + + if (err==ZIP_OK) /* number of this disk */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ + { + { + if(zi->number_entry >= 0xFFFF) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + } + } + + if (err==ZIP_OK) /* total number of entries in the central dir */ + { + if(zi->number_entry >= 0xFFFF) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + } + + if (err==ZIP_OK) /* size of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4); + + if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ + { + ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; + if(pos >= 0xffffffff) + { + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)0xffffffff,4); + } + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4); + } + + return err; +} + +int Write_GlobalComment(zip64_internal* zi, const char* global_comment); +int Write_GlobalComment(zip64_internal* zi, const char* global_comment) +{ + int err = ZIP_OK; + uInt size_global_comment = 0; + + if(global_comment != NULL) + size_global_comment = (uInt)strlen(global_comment); + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2); + + if (err == ZIP_OK && size_global_comment > 0) + { + if (ZWRITE64(zi->z_filefunc,zi->filestream, global_comment, size_global_comment) != size_global_comment) + err = ZIP_ERRNO; + } + return err; +} + +extern int ZEXPORT zipClose (zipFile file, const char* global_comment) +{ + zip64_internal* zi; + int err = 0; + uLong size_centraldir = 0; + ZPOS64_T centraldir_pos_inzip; + ZPOS64_T pos; + + if (file == NULL) + return ZIP_PARAMERROR; + + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + } + +#ifndef NO_ADDFILEINEXISTINGZIP + if (global_comment==NULL) + global_comment = zi->globalcomment; +#endif + + centraldir_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); + + if (err==ZIP_OK) + { + linkedlist_datablock_internal* ldi = zi->central_dir.first_block; + while (ldi!=NULL) + { + if ((err==ZIP_OK) && (ldi->filled_in_this_block>0)) + { + if (ZWRITE64(zi->z_filefunc,zi->filestream, ldi->data, ldi->filled_in_this_block) != ldi->filled_in_this_block) + err = ZIP_ERRNO; + } + + size_centraldir += ldi->filled_in_this_block; + ldi = ldi->next_datablock; + } + } + free_linkedlist(&(zi->central_dir)); + + pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; + if(pos >= 0xffffffff) + { + ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream); + Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); + + Write_Zip64EndOfCentralDirectoryLocator(zi, Zip64EOCDpos); + } + + if (err==ZIP_OK) + err = Write_EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); + + if(err == ZIP_OK) + err = Write_GlobalComment(zi, global_comment); + + if (ZCLOSE64(zi->z_filefunc,zi->filestream) != 0) + if (err == ZIP_OK) + err = ZIP_ERRNO; + +#ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(zi->globalcomment); +#endif + TRYFREE(zi); + + return err; +} + +extern int ZEXPORT zipRemoveExtraInfoBlock (char* pData, int* dataLen, short sHeader) +{ + char* p = pData; + int size = 0; + char* pNewHeader; + char* pTmp; + short header; + short dataSize; + + int retVal = ZIP_OK; + + if(pData == NULL || *dataLen < 4) + return ZIP_PARAMERROR; + + pNewHeader = (char*)ALLOC(*dataLen); + pTmp = pNewHeader; + + while(p < (pData + *dataLen)) + { + header = *(short*)p; + dataSize = *(((short*)p)+1); + + if( header == sHeader ) // Header found. + { + p += dataSize + 4; // skip it. do not copy to temp buffer + } + else + { + // Extra Info block should not be removed, So copy it to the temp buffer. + memcpy(pTmp, p, dataSize + 4); + p += dataSize + 4; + size += dataSize + 4; + } + + } + + if(size < *dataLen) + { + // clean old extra info block. + memset(pData,0, *dataLen); + + // copy the new extra info block over the old + if(size > 0) + memcpy(pData, pNewHeader, size); + + // set the new extra info size + *dataLen = size; + + retVal = ZIP_OK; + } + else + retVal = ZIP_ERRNO; + + TRYFREE(pNewHeader); + + return retVal; +} diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index fdc2facc8..2630f2615 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -223,7 +223,7 @@ - + diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 3f2d96b50..599bf82f1 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -169,6 +169,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItemWithTitle:NSLocalizedString(@"About vChewing…", @"") action:@selector(showAbout:) keyEquivalent:@""]; if (optionKeyPressed) { [menu addItemWithTitle:NSLocalizedString(@"Reboot vChewing…", @"") action:@selector(selfTerminate:) keyEquivalent:@""]; + [menu addItemWithTitle:NSLocalizedString(@"Deploy Zip Data…", @"") action:@selector(cnsDeploy:) keyEquivalent:@""]; } return menu; } @@ -1550,6 +1551,11 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } return YES; } +- (void)cnsDeploy:(id)sender +{ + [LanguageModelManager deployZipDataFile:@"UNICHARS"]; +} + - (void)_openUserFile:(NSString *)path { if (![self _checkUserFiles]) { diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index 58b39fd86..f186fef91 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -10,12 +10,14 @@ #import "FastLM.h" #import "UserOverrideModel.h" #import "vChewingLM.h" +#import "SSZipArchive.h" NS_ASSUME_NONNULL_BEGIN @interface LanguageModelManager : NSObject + (void)loadDataModels; ++ (void)deployZipDataFile:(NSString *)filenameWithoutExtension; + (void)loadUserPhrases; + (void)loadUserPhraseReplacement; + (BOOL)checkIfUserLanguageModelFilesExist; @@ -23,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN + (NSString *)userPhrasesDataPath:(NSString *)inputMode; + (NSString *)excludedPhrasesDataPath:(NSString *)inputMode; + (NSString *)phraseReplacementDataPath:(NSString *)inputMode; ++ (NSString *)cnsDataPath:(NSString *)inputMode; @property (class, readonly, nonatomic) NSString *dataFolderPath; @property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelCoreCHT; diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 6ba4b0db6..751ecf34e 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -39,6 +39,14 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing lm.loadLanguageModel([dataPath UTF8String]); } ++ (void)deployZipDataFile:(NSString *)filenameWithoutExtension +{ + Class cls = NSClassFromString(@"vChewingInputMethodController"); + NSString *zipPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"zip"]; + NSString *destinationPath = [self dataFolderPath]; + [SSZipArchive unzipFileAtPath:zipPath toDestination:destinationPath]; +} + + (void)loadDataModels { LTLoadLanguageModelFile(@"data-cht", glanguageModelCoreCHT); @@ -193,7 +201,12 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return [[self dataFolderPath] stringByAppendingPathComponent:fileName]; } - + (vChewingLM *)languageModelCoreCHT ++ (NSString *)cnsDataPath:(NSString *)inputMode +{ + return [[self dataFolderPath] stringByAppendingPathComponent:@"UNICHARS.csv"]; +} + ++ (vChewingLM *)languageModelCoreCHT { return &glanguageModelCoreCHT; } diff --git a/Source/en.lproj/preferences.strings b/Source/en.lproj/preferences.strings index f5952af78..a2914d5b6 100644 --- a/Source/en.lproj/preferences.strings +++ b/Source/en.lproj/preferences.strings @@ -137,8 +137,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "Output Settings"; -/* Class = "NSButtonCell"; title = "Enable CNS11643 Support"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "Enable CNS11643 Support"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "Enable CNS11643 Support (2022-01-07)"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "Keyboard Layout"; diff --git a/Source/ja.lproj/preferences.strings b/Source/ja.lproj/preferences.strings index 522b7de27..8269e9fbe 100644 --- a/Source/ja.lproj/preferences.strings +++ b/Source/ja.lproj/preferences.strings @@ -137,8 +137,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "出力設定"; -/* Class = "NSButtonCell"; title = "Enable CNS11643 Support"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "全字庫モード // ほぼ全ての Unicode 漢字を入力できる"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "全字庫モード // 入力可能の漢字数倍増 (2022-01-07)"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "キーボード配列"; diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index 7fc9cd38d..e92bf4574 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -11,6 +11,7 @@ // #import // @import Foundation; +#import "SSZipArchive.h" // Zip Archive Support; @interface LanguageModelManager : NSObject + (void)loadDataModels; + (void)loadUserPhrases; diff --git a/Source/zh-Hans.lproj/preferences.strings b/Source/zh-Hans.lproj/preferences.strings index 01962b0ea..4e25b1cde 100644 --- a/Source/zh-Hans.lproj/preferences.strings +++ b/Source/zh-Hans.lproj/preferences.strings @@ -137,8 +137,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "输出设定"; -/* Class = "NSButtonCell"; title = "Enable CNS11643 Support"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "启用 CNS11643 全字库支援"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "启用 CNS11643 全字库支援 (2022-01-07)"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "键盘布局"; diff --git a/Source/zh-Hant.lproj/preferences.strings b/Source/zh-Hant.lproj/preferences.strings index f891f0449..9938b2d89 100644 --- a/Source/zh-Hant.lproj/preferences.strings +++ b/Source/zh-Hant.lproj/preferences.strings @@ -137,8 +137,8 @@ /* Class = "NSBox"; title = "Output Settings"; ObjectID = "Uyz-xL-TVN"; */ "Uyz-xL-TVN.title" = "輸出設定"; -/* Class = "NSButtonCell"; title = "Enable CNS11643 Support"; ObjectID = "W24-T4-cg0"; */ -"W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援"; +/* Class = "NSButtonCell"; title = "Enable CNS11643 Support (2022-01-07)"; ObjectID = "W24-T4-cg0"; */ +"W24-T4-cg0.title" = "啟用 CNS11643 全字庫支援 (2022-01-07)"; /* Class = "NSBox"; title = "Keyboard Layout"; ObjectID = "Wvt-HE-LOv"; */ "Wvt-HE-LOv.title" = "鍵盤佈局"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 06414c928..7b5a47a90 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -32,6 +32,14 @@ 5BD0D19F279454F60008F48E /* Beep.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19E279454F60008F48E /* Beep.aif */; }; 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */; }; 5BD13F482794F0A6000E429F /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */; }; + 5BDD25F2279D65CC00AA18F8 /* UNICHARS.zip in Resources */ = {isa = PBXBuildFile; fileRef = 5BDD25F1279D65CB00AA18F8 /* UNICHARS.zip */; }; + 5BDD25F4279D678600AA18F8 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BDD25F3279D677F00AA18F8 /* libz.tbd */; }; + 5BDD25F5279D6CFE00AA18F8 /* AWFileHash.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E3279D64FB00AA18F8 /* AWFileHash.m */; }; + 5BDD25F6279D6D0200AA18F8 /* SSZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25F0279D64FB00AA18F8 /* SSZipArchive.m */; }; + 5BDD25F7279D6D1200AA18F8 /* unzip.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E6279D64FB00AA18F8 /* unzip.m */; }; + 5BDD25F8279D6D1200AA18F8 /* zip.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E7279D64FB00AA18F8 /* zip.m */; }; + 5BDD25F9279D6D1200AA18F8 /* ioapi.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E8279D64FB00AA18F8 /* ioapi.m */; }; + 5BDD25FA279D6D1200AA18F8 /* mztools.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E9279D64FB00AA18F8 /* mztools.m */; }; 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; @@ -135,6 +143,21 @@ 5BD0D19927943D390008F48E /* clsSFX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = clsSFX.swift; sourceTree = ""; }; 5BD0D19E279454F60008F48E /* Beep.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.aif; sourceTree = ""; }; 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = clsOOBEDefaults.swift; sourceTree = ""; }; + 5BDD25E2279D64FB00AA18F8 /* AWFileHash.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AWFileHash.h; sourceTree = ""; }; + 5BDD25E3279D64FB00AA18F8 /* AWFileHash.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AWFileHash.m; sourceTree = ""; }; + 5BDD25E6279D64FB00AA18F8 /* unzip.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = unzip.m; sourceTree = ""; }; + 5BDD25E7279D64FB00AA18F8 /* zip.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = zip.m; sourceTree = ""; }; + 5BDD25E8279D64FB00AA18F8 /* ioapi.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ioapi.m; sourceTree = ""; }; + 5BDD25E9279D64FB00AA18F8 /* mztools.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = mztools.m; sourceTree = ""; }; + 5BDD25EA279D64FB00AA18F8 /* crypt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = crypt.h; sourceTree = ""; }; + 5BDD25EB279D64FB00AA18F8 /* zip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip.h; sourceTree = ""; }; + 5BDD25EC279D64FB00AA18F8 /* unzip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = unzip.h; sourceTree = ""; }; + 5BDD25ED279D64FB00AA18F8 /* mztools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mztools.h; sourceTree = ""; }; + 5BDD25EE279D64FB00AA18F8 /* ioapi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ioapi.h; sourceTree = ""; }; + 5BDD25EF279D64FB00AA18F8 /* SSZipArchive.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SSZipArchive.h; sourceTree = ""; }; + 5BDD25F0279D64FB00AA18F8 /* SSZipArchive.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SSZipArchive.m; sourceTree = ""; }; + 5BDD25F1279D65CB00AA18F8 /* UNICHARS.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; name = UNICHARS.zip; path = Data/components/common/UNICHARS.zip; sourceTree = ""; }; + 5BDD25F3279D677F00AA18F8 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = ""; }; 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; @@ -233,6 +256,7 @@ buildActionMask = 2147483647; files = ( 6A38BC2815FC158A00A8A51F /* InputMethodKit.framework in Frameworks */, + 5BDD25F4279D678600AA18F8 /* libz.tbd in Frameworks */, D48550A325EBE689006A204C /* OpenCC in Frameworks */, 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */, ); @@ -292,6 +316,50 @@ path = SFX; sourceTree = ""; }; + 5BDD25E0279D64FB00AA18F8 /* 3rdParty */ = { + isa = PBXGroup; + children = ( + 5BDD25E1279D64FB00AA18F8 /* AWFileHash */, + 5BDD25E4279D64FB00AA18F8 /* SSZipArchive */, + ); + path = 3rdParty; + sourceTree = ""; + }; + 5BDD25E1279D64FB00AA18F8 /* AWFileHash */ = { + isa = PBXGroup; + children = ( + 5BDD25E2279D64FB00AA18F8 /* AWFileHash.h */, + 5BDD25E3279D64FB00AA18F8 /* AWFileHash.m */, + ); + path = AWFileHash; + sourceTree = ""; + }; + 5BDD25E4279D64FB00AA18F8 /* SSZipArchive */ = { + isa = PBXGroup; + children = ( + 5BDD25E5279D64FB00AA18F8 /* minizip */, + 5BDD25EF279D64FB00AA18F8 /* SSZipArchive.h */, + 5BDD25F0279D64FB00AA18F8 /* SSZipArchive.m */, + ); + path = SSZipArchive; + sourceTree = ""; + }; + 5BDD25E5279D64FB00AA18F8 /* minizip */ = { + isa = PBXGroup; + children = ( + 5BDD25E6279D64FB00AA18F8 /* unzip.m */, + 5BDD25E7279D64FB00AA18F8 /* zip.m */, + 5BDD25E8279D64FB00AA18F8 /* ioapi.m */, + 5BDD25E9279D64FB00AA18F8 /* mztools.m */, + 5BDD25EA279D64FB00AA18F8 /* crypt.h */, + 5BDD25EB279D64FB00AA18F8 /* zip.h */, + 5BDD25EC279D64FB00AA18F8 /* unzip.h */, + 5BDD25ED279D64FB00AA18F8 /* mztools.h */, + 5BDD25EE279D64FB00AA18F8 /* ioapi.h */, + ); + path = minizip; + sourceTree = ""; + }; 5BE798A12792E50F00337FF9 /* UI */ = { isa = PBXGroup; children = ( @@ -339,6 +407,7 @@ 6A0D4EA515FC0D2D00ABF4B3 /* Frameworks */ = { isa = PBXGroup; children = ( + 5BDD25F3279D677F00AA18F8 /* libz.tbd */, 6A0D4EA915FC0D2D00ABF4B3 /* AppKit.framework */, 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */, 6A0D4EAB15FC0D2D00ABF4B3 /* Foundation.framework */, @@ -350,6 +419,7 @@ 6A0D4EC215FC0D3C00ABF4B3 /* Source */ = { isa = PBXGroup; children = ( + 5BDD25E0279D64FB00AA18F8 /* 3rdParty */, 5BE798A12792E50F00337FF9 /* UI */, 5B58E87D278413E7003EA2AD /* BSDLicense.txt */, 6A38BBDD15FC115800A8A51F /* Data */, @@ -480,6 +550,7 @@ 6A0D4F4715FC0EB900ABF4B3 /* Resources */ = { isa = PBXGroup; children = ( + 5BDD25F1279D65CB00AA18F8 /* UNICHARS.zip */, 5BD0D19E279454F60008F48E /* Beep.aif */, 5BD0D19327940E9D0008F48E /* Fart.aif */, 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */, @@ -636,6 +707,7 @@ 6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */, 6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */, 5BD0D19427940E9D0008F48E /* Fart.aif in Resources */, + 5BDD25F2279D65CC00AA18F8 /* UNICHARS.zip in Resources */, 6A38BC1515FC117A00A8A51F /* data-cht.txt in Resources */, 6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */, 5B58E87F278413E7003EA2AD /* BSDLicense.txt in Resources */, @@ -690,7 +762,10 @@ files = ( 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */, 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */, + 5BDD25F5279D6CFE00AA18F8 /* AWFileHash.m in Sources */, + 5BDD25F8279D6D1200AA18F8 /* zip.m in Sources */, 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */, + 5BDD25F9279D6D1200AA18F8 /* ioapi.m in Sources */, 5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */, 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, @@ -699,9 +774,11 @@ 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */, 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */, 5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */, + 5BDD25F6279D6D0200AA18F8 /* SSZipArchive.m in Sources */, 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, + 5BDD25F7279D6D1200AA18F8 /* unzip.m in Sources */, 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, @@ -711,6 +788,7 @@ 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */, 5B217128279BB22700F91A2B /* frmAboutWindow.swift in Sources */, 5BD13F482794F0A6000E429F /* PhraseReplacementMap.cpp in Sources */, + 5BDD25FA279D6D1200AA18F8 /* mztools.m in Sources */, 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, -- Gitee From 78c90cadeab6250e3f676e523695ef8c4c0e98f6 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 24 Jan 2022 10:30:49 +0800 Subject: [PATCH 107/163] CNS // Phase 2: + CNSLM (with Debug Messaging System). --- Source/Engine/LanguageModel/CNSLM.cpp | 131 +++++++++++++++++++++ Source/Engine/LanguageModel/CNSLM.h | 48 ++++++++ Source/Engine/LanguageModel/vChewingLM.cpp | 8 ++ Source/Engine/LanguageModel/vChewingLM.h | 6 +- Source/LanguageModelManager.h | 3 +- Source/LanguageModelManager.mm | 22 ++-- vChewing.xcodeproj/project.pbxproj | 6 + 7 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 Source/Engine/LanguageModel/CNSLM.cpp create mode 100644 Source/Engine/LanguageModel/CNSLM.h diff --git a/Source/Engine/LanguageModel/CNSLM.cpp b/Source/Engine/LanguageModel/CNSLM.cpp new file mode 100644 index 000000000..6c184821f --- /dev/null +++ b/Source/Engine/LanguageModel/CNSLM.cpp @@ -0,0 +1,131 @@ +/* + * CNSLM.cpp + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +#include "CNSLM.h" + +#include +#include +#include +#include +#include +#include + +#include "KeyValueBlobReader.h" + +namespace vChewing { + +CNSLM::CNSLM() + : fd(-1) + , data(0) + , length(0) +{ +} + +CNSLM::~CNSLM() +{ + if (data) { + close(); + } +} + +bool CNSLM::open(const char *path) +{ + if (data) { + syslog(LOG_CONS, "CNSLM: Failed at Open Step 1.\n"); + return false; + } + + fd = ::open(path, O_RDONLY); + if (fd == -1) { + syslog(LOG_CONS, "CNSLM: Failed at Open Step 2.\n"); + printf("open:: file not exist"); + return false; + } + + struct stat sb; + if (fstat(fd, &sb) == -1) { + syslog(LOG_CONS, "CNSLM: Failed at Open Step 3.\n"); + printf("open:: cannot open file"); + return false; + } + + length = (size_t)sb.st_size; + + data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0); + if (!data) { + ::close(fd); + syslog(LOG_CONS, "CNSLM: Failed at Open Step 4.\n"); + return false; + } + + KeyValueBlobReader reader(static_cast(data), length); + KeyValueBlobReader::KeyValue keyValue; + KeyValueBlobReader::State state; + while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) { + // We invert the key and value, since in user phrases, "key" is the phrase value, and "value" is the BPMF reading. + keyRowMap[keyValue.value].emplace_back(keyValue.value, keyValue.key); + } + // 下面這一段或許可以做成開關、來詢問是否對使用者語彙採取寬鬆策略(哪怕有行內容寫錯也會放行) + if (state == KeyValueBlobReader::State::ERROR) { + // close(); + syslog(LOG_CONS, "CNSLM: Failed at Open Step 5. On Error Resume Next.\n"); + // return false; + } + return true; +} + +void CNSLM::close() +{ + if (data) { + munmap(data, length); + ::close(fd); + data = 0; + } + + keyRowMap.clear(); +} + +void CNSLM::dump() +{ + for (const auto& entry : keyRowMap) { + const std::vector& rows = entry.second; + for (const auto& row : rows) { + std::cerr << row.key << " " << row.value << "\n"; + } + } +} + +const std::vector CNSLM::bigramsForKeys(const std::string& preceedingKey, const std::string& key) +{ + return std::vector(); +} + +const std::vector CNSLM::unigramsForKey(const std::string& key) +{ + std::vector v; + auto iter = keyRowMap.find(key); + if (iter != keyRowMap.end()) { + const std::vector& rows = iter->second; + for (const auto& row : rows) { + Taiyan::Gramambular::Unigram g; + g.keyValue.key = row.key; + g.keyValue.value = row.value; + g.score = -17.0; + v.push_back(g); + } + } + + return v; +} + +bool CNSLM::hasUnigramsForKey(const std::string& key) +{ + return keyRowMap.find(key) != keyRowMap.end(); +} + +}; // namespace vChewing diff --git a/Source/Engine/LanguageModel/CNSLM.h b/Source/Engine/LanguageModel/CNSLM.h new file mode 100644 index 000000000..fd2d199ba --- /dev/null +++ b/Source/Engine/LanguageModel/CNSLM.h @@ -0,0 +1,48 @@ +/* + * CNSLM.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +#ifndef CNSLM_H +#define CNSLM_H + +#include +#include +#include +#include "LanguageModel.h" + +namespace vChewing { + +class CNSLM : public Taiyan::Gramambular::LanguageModel +{ +public: + CNSLM(); + ~CNSLM(); + + bool open(const char *path); + void close(); + void dump(); + + virtual const std::vector bigramsForKeys(const std::string& preceedingKey, const std::string& key); + virtual const std::vector unigramsForKey(const std::string& key); + virtual bool hasUnigramsForKey(const std::string& key); + +protected: + struct Row { + Row(std::string_view& k, std::string_view& v) : key(k), value(v) {} + std::string_view key; + std::string_view value; + }; + + std::map> keyRowMap; + int fd; + void *data; + size_t length; +}; + +} + +#endif diff --git a/Source/Engine/LanguageModel/vChewingLM.cpp b/Source/Engine/LanguageModel/vChewingLM.cpp index 8fd5b82af..d6c77be3d 100644 --- a/Source/Engine/LanguageModel/vChewingLM.cpp +++ b/Source/Engine/LanguageModel/vChewingLM.cpp @@ -32,6 +32,14 @@ void vChewingLM::loadLanguageModel(const char* languageModelDataPath) } } +void vChewingLM::loadCNSData(const char* cnsDataPath) +{ + if (cnsDataPath) { + m_cnsData.close(); + m_cnsData.open(cnsDataPath); + } +} + void vChewingLM::loadUserPhrases(const char* userPhrasesDataPath, const char* excludedPhrasesDataPath) { diff --git a/Source/Engine/LanguageModel/vChewingLM.h b/Source/Engine/LanguageModel/vChewingLM.h index ce339db5a..06feb42b5 100644 --- a/Source/Engine/LanguageModel/vChewingLM.h +++ b/Source/Engine/LanguageModel/vChewingLM.h @@ -10,8 +10,9 @@ #define VCHEWINGLM_H #include -#include "UserPhrasesLM.h" #include "FastLM.h" +#include "CNSLM.h" +#include "UserPhrasesLM.h" #include "PhraseReplacementMap.h" #include @@ -25,7 +26,9 @@ public: ~vChewingLM(); void loadLanguageModel(const char* languageModelPath); + void loadCNSData(const char* cnsDataPath); void loadUserPhrases(const char* userPhrasesPath, const char* excludedPhrasesPath); + void loadPhraseReplacementMap(const char* phraseReplacementPath); const vector bigramsForKeys(const string& preceedingKey, const string& key); @@ -41,6 +44,7 @@ protected: std::unordered_set& insertedValues); FastLM m_languageModel; + CNSLM m_cnsData; UserPhrasesLM m_userPhrases; UserPhrasesLM m_excludedPhrases; PhraseReplacementMap m_phraseReplacement; diff --git a/Source/LanguageModelManager.h b/Source/LanguageModelManager.h index f186fef91..78b541d18 100644 --- a/Source/LanguageModelManager.h +++ b/Source/LanguageModelManager.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN + (void)loadDataModels; + (void)deployZipDataFile:(NSString *)filenameWithoutExtension; ++ (void)loadCNSData; + (void)loadUserPhrases; + (void)loadUserPhraseReplacement; + (BOOL)checkIfUserLanguageModelFilesExist; @@ -25,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN + (NSString *)userPhrasesDataPath:(NSString *)inputMode; + (NSString *)excludedPhrasesDataPath:(NSString *)inputMode; + (NSString *)phraseReplacementDataPath:(NSString *)inputMode; -+ (NSString *)cnsDataPath:(NSString *)inputMode; ++ (NSString *)cnsDataPath; @property (class, readonly, nonatomic) NSString *dataFolderPath; @property (class, readonly, nonatomic) vChewing::vChewingLM *languageModelCoreCHT; diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 751ecf34e..2684e352b 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -32,13 +32,6 @@ static NSString *const kBopomofoModeIdentifierCHS = @"org.atelierInmu.inputmetho @implementation LanguageModelManager -static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewingLM &lm) -{ - Class cls = NSClassFromString(@"vChewingInputMethodController"); - NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"]; - lm.loadLanguageModel([dataPath UTF8String]); -} - + (void)deployZipDataFile:(NSString *)filenameWithoutExtension { Class cls = NSClassFromString(@"vChewingInputMethodController"); @@ -47,12 +40,25 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing [SSZipArchive unzipFileAtPath:zipPath toDestination:destinationPath]; } +static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewingLM &lm) +{ + Class cls = NSClassFromString(@"vChewingInputMethodController"); + NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"]; + lm.loadLanguageModel([dataPath UTF8String]); +} + + (void)loadDataModels { LTLoadLanguageModelFile(@"data-cht", glanguageModelCoreCHT); LTLoadLanguageModelFile(@"data-chs", glanguageModelCoreCHS); } ++ (void)loadCNSData +{ + glanguageModelCoreCHT.loadCNSData([[self cnsDataPath] UTF8String]); + glanguageModelCoreCHS.loadCNSData([[self cnsDataPath] UTF8String]); +} + + (void)loadUserPhrases { glanguageModelCoreCHT.loadUserPhrases([[self userPhrasesDataPath:kBopomofoModeIdentifierCHT] UTF8String], [[self excludedPhrasesDataPath:kBopomofoModeIdentifierCHT] UTF8String]); @@ -201,7 +207,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return [[self dataFolderPath] stringByAppendingPathComponent:fileName]; } -+ (NSString *)cnsDataPath:(NSString *)inputMode ++ (NSString *)cnsDataPath { return [[self dataFolderPath] stringByAppendingPathComponent:@"UNICHARS.csv"]; } diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 7b5a47a90..4b401bef9 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 5BDD25F8279D6D1200AA18F8 /* zip.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E7279D64FB00AA18F8 /* zip.m */; }; 5BDD25F9279D6D1200AA18F8 /* ioapi.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E8279D64FB00AA18F8 /* ioapi.m */; }; 5BDD25FA279D6D1200AA18F8 /* mztools.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E9279D64FB00AA18F8 /* mztools.m */; }; + 5BDD25FD279D6D6300AA18F8 /* CNSLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25FB279D6D6200AA18F8 /* CNSLM.cpp */; }; 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; @@ -158,6 +159,8 @@ 5BDD25F0279D64FB00AA18F8 /* SSZipArchive.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SSZipArchive.m; sourceTree = ""; }; 5BDD25F1279D65CB00AA18F8 /* UNICHARS.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; name = UNICHARS.zip; path = Data/components/common/UNICHARS.zip; sourceTree = ""; }; 5BDD25F3279D677F00AA18F8 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 5BDD25FB279D6D6200AA18F8 /* CNSLM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CNSLM.cpp; sourceTree = ""; }; + 5BDD25FC279D6D6300AA18F8 /* CNSLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CNSLM.h; sourceTree = ""; }; 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = ""; }; 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; 5BDF2D022791C71200838ADB /* NonModalAlertWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonModalAlertWindowController.swift; sourceTree = ""; }; @@ -276,6 +279,8 @@ 5BA8DAFE27928120009C9FFF /* LanguageModel */ = { isa = PBXGroup; children = ( + 5BDD25FB279D6D6200AA18F8 /* CNSLM.cpp */, + 5BDD25FC279D6D6300AA18F8 /* CNSLM.h */, 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */, 5B5F4F8C27928F9300922DC2 /* vChewingLM.h */, 6A0421A615FEF3F50061ED63 /* FastLM.cpp */, @@ -792,6 +797,7 @@ 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, + 5BDD25FD279D6D6300AA18F8 /* CNSLM.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -- Gitee From 12a4436ea4e0efd41348ba9b56ff22ef92a05d09 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 22 Jan 2022 21:44:04 +0800 Subject: [PATCH 108/163] CNS // Phase 3: + Pref Entries & OOBE. --- Source/Base.lproj/preferences.xib | 3 +++ Source/Engine/vChewing/clsOOBEDefaults.swift | 6 ++++++ Source/PreferencesModule.swift | 11 +++++++++++ 3 files changed, 20 insertions(+) diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index 2630f2615..f5922a7b3 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -227,6 +227,9 @@ + + + + + + diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index ba583745c..cd6267df4 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -13,6 +13,7 @@ private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout" // alphan private let kFunctionKeyKeyboardLayoutPreferenceKey = "FunctionKeyKeyboardLayout" // alphanumeric ("ASCII") input basi private let kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = "FunctionKeyKeyboardLayoutOverrideIncludeShift" // whether include shif private let kCandidateListTextSizeKey = "CandidateListTextSize" +private let kAppleLanguagesPreferencesKey = "AppleLanguages" private let kSelectPhraseAfterCursorAsCandidatePreferenceKey = "SelectPhraseAfterCursorAsCandidate" private let kUseHorizontalCandidateListPreferenceKey = "UseHorizontalCandidateList" private let kComposingBufferSizePreferenceKey = "ComposingBufferSize" @@ -172,6 +173,7 @@ struct ComposingBufferSize { defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutPreferenceKey) defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey) defaults.removeObject(forKey: kCandidateListTextSizeKey) + defaults.removeObject(forKey: kAppleLanguagesPreferencesKey) defaults.removeObject(forKey: kSelectPhraseAfterCursorAsCandidatePreferenceKey) defaults.removeObject(forKey: kUseHorizontalCandidateListPreferenceKey) defaults.removeObject(forKey: kComposingBufferSizePreferenceKey) @@ -189,6 +191,9 @@ struct ComposingBufferSize { defaults.removeObject(forKey: kMaxCandidateLength) defaults.removeObject(forKey: kShouldNotFartInLieuOfBeep) } + + @UserDefault(key: kAppleLanguagesPreferencesKey, defaultValue: []) + @objc static var appleLanguages: Array @UserDefault(key: kKeyboardLayoutPreferenceKey, defaultValue: 0) @objc static var keyboardLayout: Int diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index d2f41522b..0da4c3322 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -22,11 +22,40 @@ extension RangeReplaceableCollection where Element: Hashable { // the "InputMethodServerPreferencesWindowControllerClass" in Info.plist. @objc(PreferencesWindowController) class PreferencesWindowController: NSWindowController { @IBOutlet weak var fontSizePopUpButton: NSPopUpButton! + @IBOutlet weak var uiLanguageButton: NSPopUpButton! @IBOutlet weak var basisKeyboardLayoutButton: NSPopUpButton! @IBOutlet weak var selectionKeyComboBox: NSComboBox! @IBOutlet weak var clickedWhetherIMEShouldNotFartToggle: NSButton! + + var currentLanguageSelectItem: NSMenuItem? = nil override func awakeFromNib() { + let languages = ["auto", "en-US", "zh-CN", "zh-TW", "ja-JP"] + var autoSelectItem: NSMenuItem? = nil + var chosenLanguageItem: NSMenuItem? = nil + uiLanguageButton.menu?.removeAllItems() + + let appleLanguages = Preferences.appleLanguages + for language in languages { + let menuItem = NSMenuItem() + menuItem.title = NSLocalizedString(language, comment: "") + menuItem.representedObject = language + + if language == "auto" { + autoSelectItem = menuItem + } + + if !appleLanguages.isEmpty { + if appleLanguages[0] == language { + chosenLanguageItem = menuItem + } + } + uiLanguageButton.menu?.addItem(menuItem) + } + + currentLanguageSelectItem = chosenLanguageItem ?? autoSelectItem + uiLanguageButton.select(currentLanguageSelectItem) + let list = TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource] var usKeyboardLayoutItem: NSMenuItem? = nil var chosenItem: NSMenuItem? = nil @@ -124,6 +153,25 @@ extension RangeReplaceableCollection where Element: Hashable { Preferences.basisKeyboardLayout = sourceID } } + + @IBAction func updateUiLanguageAction(_ sender: Any) { + if let selectItem = uiLanguageButton.selectedItem { + if currentLanguageSelectItem == selectItem { + return + } + } + if let language = uiLanguageButton.selectedItem?.representedObject as? String { + if (language != "auto") { + Preferences.appleLanguages = [language] + } + else { + UserDefaults.standard.removeObject(forKey: "AppleLanguages") + } + + NSLog("vChewing App self-terminated due to UI language change.") + NSApplication.shared.terminate(nil) + } + } @IBAction func clickedWhetherIMEShouldNotFartToggleAction(_ sender: Any) { clsSFX.beep() -- Gitee From abafc43be2d909edf43caabbf9b8111680b20901 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 25 Jan 2022 11:51:54 +0800 Subject: [PATCH 120/163] Shiki: MUI Selector // phase 2: Localization. --- Source/PreferencesWindowController.swift | 2 +- Source/en.lproj/Localizable.strings | 5 +++++ Source/en.lproj/preferences.strings | 4 ++-- Source/ja.lproj/Localizable.strings | 5 +++++ Source/ja.lproj/preferences.strings | 4 ++-- Source/zh-Hans.lproj/Localizable.strings | 5 +++++ Source/zh-Hans.lproj/preferences.strings | 4 ++-- Source/zh-Hant.lproj/Localizable.strings | 5 +++++ Source/zh-Hant.lproj/preferences.strings | 4 ++-- 9 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index 0da4c3322..b0e829003 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -30,7 +30,7 @@ extension RangeReplaceableCollection where Element: Hashable { var currentLanguageSelectItem: NSMenuItem? = nil override func awakeFromNib() { - let languages = ["auto", "en-US", "zh-CN", "zh-TW", "ja-JP"] + let languages = ["auto", "en", "zh-Hans", "zh-Hant", "ja"] var autoSelectItem: NSMenuItem? = nil var chosenLanguageItem: NSMenuItem? = nil uiLanguageButton.menu?.removeAllItems() diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index ed2bd2f9f..016d461c6 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -37,3 +37,8 @@ "NT351 BPMF EMU" = "NT351 Per-Char Select Mode"; "CNS11643 Mode" = "CNS11643 Mode"; "Reboot vChewing…" = "Reboot vChewing…"; +"auto" = "Follow System Settings"; +"en" = "English"; +"zh-Hans" = "Simplified Chinese"; +"zh-Hant" = "Traditional Chinese"; +"ja" = "Japanese"; diff --git a/Source/en.lproj/preferences.strings b/Source/en.lproj/preferences.strings index a2914d5b6..54ad44cf6 100644 --- a/Source/en.lproj/preferences.strings +++ b/Source/en.lproj/preferences.strings @@ -146,8 +146,8 @@ /* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ "Z9t-P0-BLF.title" = "Check for updates automatically"; -/* Class = "NSTextFieldCell"; title = "Change user interface language."; ObjectID = "ZEv-Q2-mYL"; */ -"ZEv-Q2-mYL.title" = "Change user interface language."; +/* Class = "NSTextFieldCell"; title = "Change user interface language (will reboot the IME)."; ObjectID = "ZEv-Q2-mYL"; */ +"ZEv-Q2-mYL.title" = "Change user interface language (will reboot the IME)."; /* Class = "NSMenuItem"; title = "Simplified Chinese"; ObjectID = "akC-2g-ybz"; */ "akC-2g-ybz.title" = "Simplified Chinese"; diff --git a/Source/ja.lproj/Localizable.strings b/Source/ja.lproj/Localizable.strings index 1f32665e4..1c34d83c8 100644 --- a/Source/ja.lproj/Localizable.strings +++ b/Source/ja.lproj/Localizable.strings @@ -37,3 +37,8 @@ "NT351 BPMF EMU" = "全候補入力モード"; "CNS11643 Mode" = "全字庫モード"; "Reboot vChewing…" = "入力アプリ再起動…"; +"auto" = "システム設定に準ず"; +"en" = "英語"; +"zh-Hans" = "簡體中国語"; +"zh-Hant" = "繁體中国語"; +"ja" = "和語"; diff --git a/Source/ja.lproj/preferences.strings b/Source/ja.lproj/preferences.strings index 8269e9fbe..ffedad7f3 100644 --- a/Source/ja.lproj/preferences.strings +++ b/Source/ja.lproj/preferences.strings @@ -146,8 +146,8 @@ /* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ "Z9t-P0-BLF.title" = "アプリの更新通知を受く"; -/* Class = "NSTextFieldCell"; title = "Change user interface language."; ObjectID = "ZEv-Q2-mYL"; */ -"ZEv-Q2-mYL.title" = "アプリ表示用言語をご指定ください。"; +/* Class = "NSTextFieldCell"; title = "Change user interface language (will reboot the IME)."; ObjectID = "ZEv-Q2-mYL"; */ +"ZEv-Q2-mYL.title" = "アプリ表示用言語をご指定ください、そして入力アプリは自動的に再起動。"; /* Class = "NSMenuItem"; title = "Simplified Chinese"; ObjectID = "akC-2g-ybz"; */ "akC-2g-ybz.title" = "簡体中国語"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 7737bb21a..a67e8e6d2 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -37,3 +37,8 @@ "NT351 BPMF EMU" = "模拟逐字选字输入"; "CNS11643 Mode" = "全字库模式"; "Reboot vChewing…" = "重新启动输入法…"; +"auto" = "与系统设定一致"; +"en" = "英文"; +"zh-Hans" = "简体中文"; +"zh-Hant" = "繁体中文"; +"ja" = "和文"; diff --git a/Source/zh-Hans.lproj/preferences.strings b/Source/zh-Hans.lproj/preferences.strings index 4e25b1cde..37951c0d8 100644 --- a/Source/zh-Hans.lproj/preferences.strings +++ b/Source/zh-Hans.lproj/preferences.strings @@ -146,8 +146,8 @@ /* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ "Z9t-P0-BLF.title" = "自动检查软体更新"; -/* Class = "NSTextFieldCell"; title = "Change user interface language."; ObjectID = "ZEv-Q2-mYL"; */ -"ZEv-Q2-mYL.title" = "变更使用者介面语言。"; +/* Class = "NSTextFieldCell"; title = "Change user interface language (will reboot the IME)."; ObjectID = "ZEv-Q2-mYL"; */ +"ZEv-Q2-mYL.title" = "变更使用者介面语言,会自动重新启动输入法。"; /* Class = "NSMenuItem"; title = "Simplified Chinese"; ObjectID = "akC-2g-ybz"; */ "akC-2g-ybz.title" = "简体中文"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 6abccb63d..79fa9be8d 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -37,3 +37,8 @@ "NT351 BPMF EMU" = "模擬逐字選字輸入"; "CNS11643 Mode" = "全字庫模式"; "Reboot vChewing…" = "重新啟動輸入法…"; +"auto" = "與系統設定一致"; +"en" = "英文"; +"zh-Hans" = "簡體中文"; +"zh-Hant" = "繁體中文"; +"ja" = "和文"; diff --git a/Source/zh-Hant.lproj/preferences.strings b/Source/zh-Hant.lproj/preferences.strings index 9938b2d89..a894f35b6 100644 --- a/Source/zh-Hant.lproj/preferences.strings +++ b/Source/zh-Hant.lproj/preferences.strings @@ -146,8 +146,8 @@ /* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ "Z9t-P0-BLF.title" = "自動檢查軟體更新"; -/* Class = "NSTextFieldCell"; title = "Change user interface language."; ObjectID = "ZEv-Q2-mYL"; */ -"ZEv-Q2-mYL.title" = "變更使用者介面語言。"; +/* Class = "NSTextFieldCell"; title = "Change user interface language (will reboot the IME)."; ObjectID = "ZEv-Q2-mYL"; */ +"ZEv-Q2-mYL.title" = "變更使用者介面語言,會自動重新啟動輸入法。"; /* Class = "NSMenuItem"; title = "Simplified Chinese"; ObjectID = "akC-2g-ybz"; */ "akC-2g-ybz.title" = "簡體中文"; -- Gitee From 32b9c1f1e569e211a87300945b660135a7fa6bee Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 25 Jan 2022 20:27:01 +0800 Subject: [PATCH 121/163] Installer // AppDelegate Localization Fix. --- Source/Installer/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Installer/AppDelegate.swift b/Source/Installer/AppDelegate.swift index 21d0268b3..cd4e2dafb 100644 --- a/Source/Installer/AppDelegate.swift +++ b/Source/Installer/AppDelegate.swift @@ -83,7 +83,7 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { } if upgrading { - installButton.title = NSLocalizedString("Agree and Upgrade", comment: "") + installButton.title = NSLocalizedString("Upgrade", comment: "") } window?.center() -- Gitee From 4e708fc813877cceb1d1f2b261aba660877287e2 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 25 Jan 2022 20:53:14 +0800 Subject: [PATCH 122/163] =?UTF-8?q?macOS=20Requirements=20=E2=86=91=20El?= =?UTF-8?q?=20Capitan.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vChewing.xcodeproj/project.pbxproj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index f5ed9060f..69169ef1f 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -969,6 +969,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; ONLY_ACTIVE_ARCH = YES; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", @@ -1009,6 +1010,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-fcxx-modules", @@ -1065,7 +1067,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1118,7 +1120,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = org.atelierInmu.inputmethod.vChewing; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -1220,7 +1222,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1260,7 +1262,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = "org.atelierInmu.vChewing.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; -- Gitee From c6e50cf8c77f8e4849e47a1f20947e2b90ebbdfa Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 25 Jan 2022 20:52:53 +0800 Subject: [PATCH 123/163] Installer // Use FileManager to trash old bundle. --- Source/Installer/AppDelegate.swift | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Source/Installer/AppDelegate.swift b/Source/Installer/AppDelegate.swift index cd4e2dafb..f4b08791f 100644 --- a/Source/Installer/AppDelegate.swift +++ b/Source/Installer/AppDelegate.swift @@ -117,12 +117,25 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { let shouldWaitForTranslocationRemoval = appBundleChronoshiftedToARandomizedPath(kTargetPartialPath) && (window?.responds(to: #selector(NSWindow.beginSheet(_:completionHandler:))) ?? false) - // http://www.cocoadev.com/index.pl?MoveToTrash - let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath - let trashDir = (NSHomeDirectory() as NSString).appendingPathComponent(".Trash") - var tag = 0 - - NSWorkspace.shared.performFileOperation(.recycleOperation, source: sourceDir, destination: trashDir, files: [kTargetBundle], tag: &tag) + // 將既存輸入法扔到垃圾桶內 + do { + let sourceDir = (kDestinationPartial as NSString).expandingTildeInPath + let fileManager = FileManager.default + let fileURLString = String(format: "%@/%@", sourceDir, kTargetBundle) + let fileURL = URL(fileURLWithPath: fileURLString) + + // 檢查檔案是否存在 + if fileManager.fileExists(atPath: fileURLString) { + // 塞入垃圾桶 + try fileManager.trashItem(at: fileURL, resultingItemURL: nil) + } else { + NSLog("File does not exist") + } + + } + catch let error as NSError { + NSLog("An error took place: \(error)") + } let killTask = Process() killTask.launchPath = "/usr/bin/killall" -- Gitee From f9e10af1e2dca360db58c6ebdda9522b8bac9192 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 26 Jan 2022 18:26:47 +0800 Subject: [PATCH 124/163] Removing unnecessary OpenVanilla components. --- .../OpenVanilla/Mandarin/Mandarin.cpp | 2 +- .../3rdParty/OpenVanilla/Mandarin/Mandarin.h | 2 +- Source/3rdParty/OpenVanilla/OVAroundFilter.h | 29 - Source/3rdParty/OpenVanilla/OVBase.h | 26 - Source/3rdParty/OpenVanilla/OVBenchmark.h | 82 -- Source/3rdParty/OpenVanilla/OVCINDataTable.h | 786 ------------------ .../OpenVanilla/OVCINDatabaseService.h | 169 ---- .../OpenVanilla/OVCINToSQLiteConvertor.h | 105 --- .../3rdParty/OpenVanilla/OVCandidateService.h | 188 ----- .../3rdParty/OpenVanilla/OVDatabaseService.h | 81 -- .../3rdParty/OpenVanilla/OVDateTimeHelper.h | 129 --- .../3rdParty/OpenVanilla/OVEncodingService.h | 52 -- .../OpenVanilla/OVEventHandlingContext.h | 82 -- Source/3rdParty/OpenVanilla/OVException.h | 26 - Source/3rdParty/OpenVanilla/OVFileHelper.h | 607 -------------- Source/3rdParty/OpenVanilla/OVFrameworkInfo.h | 72 -- Source/3rdParty/OpenVanilla/OVInputMethod.h | 29 - Source/3rdParty/OpenVanilla/OVKey.h | 283 ------- .../3rdParty/OpenVanilla/OVKeyPreprocessor.h | 29 - Source/3rdParty/OpenVanilla/OVKeyValueMap.h | 155 ---- Source/3rdParty/OpenVanilla/OVLoaderBase.h | 30 - Source/3rdParty/OpenVanilla/OVLoaderService.h | 100 --- Source/3rdParty/OpenVanilla/OVLocalization.h | 116 --- Source/3rdParty/OpenVanilla/OVModule.h | 126 --- Source/3rdParty/OpenVanilla/OVModulePackage.h | 80 -- Source/3rdParty/OpenVanilla/OVOutputFilter.h | 29 - Source/3rdParty/OpenVanilla/OVPathInfo.h | 43 - .../OpenVanilla/OVSQLiteDatabaseService.h | 229 ----- Source/3rdParty/OpenVanilla/OVSQLiteWrapper.h | 258 ------ Source/3rdParty/OpenVanilla/OVStringHelper.h | 198 ----- Source/3rdParty/OpenVanilla/OVTextBuffer.h | 82 -- Source/3rdParty/OpenVanilla/OVUTF8Helper.h | 2 +- Source/3rdParty/OpenVanilla/OVWildcard.h | 2 +- Source/3rdParty/OpenVanilla/OpenVanilla.h | 77 -- Source/InputMethodController.mm | 1 - Source/LanguageModelManager.mm | 1 - vChewing.xcodeproj/project.pbxproj | 60 -- 37 files changed, 4 insertions(+), 4364 deletions(-) delete mode 100644 Source/3rdParty/OpenVanilla/OVAroundFilter.h delete mode 100644 Source/3rdParty/OpenVanilla/OVBase.h delete mode 100644 Source/3rdParty/OpenVanilla/OVBenchmark.h delete mode 100644 Source/3rdParty/OpenVanilla/OVCINDataTable.h delete mode 100644 Source/3rdParty/OpenVanilla/OVCINDatabaseService.h delete mode 100644 Source/3rdParty/OpenVanilla/OVCINToSQLiteConvertor.h delete mode 100644 Source/3rdParty/OpenVanilla/OVCandidateService.h delete mode 100644 Source/3rdParty/OpenVanilla/OVDatabaseService.h delete mode 100644 Source/3rdParty/OpenVanilla/OVDateTimeHelper.h delete mode 100644 Source/3rdParty/OpenVanilla/OVEncodingService.h delete mode 100644 Source/3rdParty/OpenVanilla/OVEventHandlingContext.h delete mode 100644 Source/3rdParty/OpenVanilla/OVException.h delete mode 100644 Source/3rdParty/OpenVanilla/OVFileHelper.h delete mode 100644 Source/3rdParty/OpenVanilla/OVFrameworkInfo.h delete mode 100644 Source/3rdParty/OpenVanilla/OVInputMethod.h delete mode 100644 Source/3rdParty/OpenVanilla/OVKey.h delete mode 100644 Source/3rdParty/OpenVanilla/OVKeyPreprocessor.h delete mode 100644 Source/3rdParty/OpenVanilla/OVKeyValueMap.h delete mode 100644 Source/3rdParty/OpenVanilla/OVLoaderBase.h delete mode 100644 Source/3rdParty/OpenVanilla/OVLoaderService.h delete mode 100644 Source/3rdParty/OpenVanilla/OVLocalization.h delete mode 100644 Source/3rdParty/OpenVanilla/OVModule.h delete mode 100644 Source/3rdParty/OpenVanilla/OVModulePackage.h delete mode 100644 Source/3rdParty/OpenVanilla/OVOutputFilter.h delete mode 100644 Source/3rdParty/OpenVanilla/OVPathInfo.h delete mode 100644 Source/3rdParty/OpenVanilla/OVSQLiteDatabaseService.h delete mode 100644 Source/3rdParty/OpenVanilla/OVSQLiteWrapper.h delete mode 100644 Source/3rdParty/OpenVanilla/OVStringHelper.h delete mode 100644 Source/3rdParty/OpenVanilla/OVTextBuffer.h delete mode 100644 Source/3rdParty/OpenVanilla/OpenVanilla.h diff --git a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.cpp b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.cpp index 414dcce6a..5c0881ff0 100644 --- a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.cpp +++ b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.cpp @@ -2,7 +2,7 @@ * Mandarin.cpp * * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. + * All rights reserved. See "LICENSE.TXT" for details. */ #include diff --git a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.h b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.h index cbe4ebac3..8cac6dcdd 100644 --- a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.h +++ b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.h @@ -2,7 +2,7 @@ * Mandarin.h * * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. + * All rights reserved. See "LICENSE.TXT" for details. */ #ifndef Mandarin_h diff --git a/Source/3rdParty/OpenVanilla/OVAroundFilter.h b/Source/3rdParty/OpenVanilla/OVAroundFilter.h deleted file mode 100644 index bb3516c5c..000000000 --- a/Source/3rdParty/OpenVanilla/OVAroundFilter.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * OVAroundFilter.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVAroundFilter_h -#define OVAroundFilter_h - -#if defined(__APPLE__) - #include -#else - #include "OVModule.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVAroundFilter : public OVModule { - public: - virtual bool isAroundFilter() const - { - return true; - } - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVBase.h b/Source/3rdParty/OpenVanilla/OVBase.h deleted file mode 100644 index 857c739f6..000000000 --- a/Source/3rdParty/OpenVanilla/OVBase.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * OVBase.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVBase_h -#define OVBase_h - -#include -#include -#include - -namespace OpenVanilla { - using namespace std; - - class OVBase { - public: - virtual ~OVBase() - { - } - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVBenchmark.h b/Source/3rdParty/OpenVanilla/OVBenchmark.h deleted file mode 100644 index 6af49f2e6..000000000 --- a/Source/3rdParty/OpenVanilla/OVBenchmark.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * OVBenchmark.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVBenchmark_h -#define OVBenchmark_h - -#include - -namespace OpenVanilla { - using namespace std; - - class OVBenchmark - { - public: - OVBenchmark() - : m_used(false) - , m_running(false) - , m_start(0) - , m_elapsedTicks(0) - , m_elapsedSeconds(0.0) - { - } - - void start() - { - m_used = true; - m_running = true; - m_elapsedSeconds = 0.0; - m_elapsedTicks = 0; - m_start = clock(); - } - - void stop() - { - if (m_running) { - update(); - m_running = false; - } - } - - clock_t elapsedTicks() - { - if (!m_used) - return 0; - - if (m_running) - update(); - - return m_elapsedTicks; - } - - double elapsedSeconds() - { - if (!m_used) - return 0; - - if (m_running) - update(); - - return m_elapsedSeconds; - } - - protected: - void update() - { - m_elapsedTicks = clock() - m_start; - m_elapsedSeconds = static_cast(m_elapsedTicks) / CLOCKS_PER_SEC; - } - - bool m_used; - bool m_running; - clock_t m_start; - clock_t m_elapsedTicks; - double m_elapsedSeconds; - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVCINDataTable.h b/Source/3rdParty/OpenVanilla/OVCINDataTable.h deleted file mode 100644 index 1eb7da835..000000000 --- a/Source/3rdParty/OpenVanilla/OVCINDataTable.h +++ /dev/null @@ -1,786 +0,0 @@ -/* - * OVCINDataTable.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVCINDataTable_h -#define OVCINDataTable_h - -#include -#include -#include -#include -#include - -#include "OVFileHelper.h" -#include "OVUTF8Helper.h" -#include "OVWildcard.h" - -#include - -namespace OpenVanilla { - using namespace std; - - // CIN := (COMMENT | PROPERTY | KEYNAME | CHARDEF)* - // EOL := \n|\r - // COMMENT := ^#.*(EOL) - // KEY := \w+ - // VALUE := \w([\w\s]*\w)* - // PROPERTY: ^%(KEY)\s+(VALUE)(EOL) - // KEYNAME := - // ^%keyname\s+begin(EOL) - // ^(KEY)\s+(VALUE)(EOL) - // ^%keyname\s+end(EOL) - // CHARDEF: - // ^%chardef\s+begin(EOL) - // ^(KEY)\s+(VALUE)(EOL) - // ^%chardef\s+end(EOL) - - class OVCINDataTableParser; - - class OVFastKeyValuePairMap { - public: - ~OVFastKeyValuePairMap() - { - free(m_data); - } - - size_t size() - { - return m_index; - } - - pair keyValuePairAtIndex(size_t index) - { - if (index >= m_index) - return pair(); - - KVPair* entry = m_data + index; - return pair(entry->key, entry->value); - } - - vector > findPairsWithKey(const char* key) - { - return fetchValuesFromIndex(findFirstOccuranceOfKey(key), key); - } - - vector > findPairsWithWildcard(const OVWildcard& pWildcard) - { - const OVWildcard* ptrWildcard = &pWildcard; - - if (pWildcard.isCaseSensitive() != m_caseSensitive) { - OVWildcard newWildcard(pWildcard.expression(), pWildcard.matchOneChar(), pWildcard.matchZeroOrMoreChar(), m_caseSensitive); - ptrWildcard = &newWildcard; - } - - const OVWildcard& wildcard = *ptrWildcard; - - string headString = wildcard.longestHeadMatchString(); - insensitivizeString(headString); - size_t hSLength = headString.length(); - - size_t start = findFirstOccuranceOfKey(headString.c_str(), true); - vector > result; - - for (size_t index = start; index < m_index; index++) { - KVPair* entry = m_data + index; - string keyString = entry->key; - - // if no more head matchZeroOrMoreChar - string keySubstr = keyString.substr(0, hSLength); - insensitivizeString(keySubstr); - if (keySubstr > headString) - break; - - if (wildcard.match(keyString)) - result.push_back(pair(keyString, entry->value)); - } - - return result; - } - protected: - friend class OVCINDataTableParser; - - OVFastKeyValuePairMap(size_t initSize, size_t growSize, bool caseSensitive = false) - : m_index(0) - , m_size(initSize ? initSize : 1) - , m_growSize(growSize) - , m_caseSensitive(caseSensitive) - { - m_data = (KVPair*)calloc(1, sizeof(KVPair) * m_size); - } - - - void add(char* key, char* value) - { - KVPair* entry = m_data + m_index; - entry->key = key; - entry->value = value; - - m_index++; - if (m_index == m_size) - grow(); - } - - void sortAndFreeze() - { - m_size = m_index; - - if (m_caseSensitive) - qsort(m_data, m_index, sizeof(KVPair), OVFastKeyValuePairMap::qsortCompareCaseSensitive); - else - qsort(m_data, m_index, sizeof(KVPair), OVFastKeyValuePairMap::qsortCompare); - } - - void insensitivizeString(string& str) - { - if (m_caseSensitive) - return; - - for (string::iterator iter = str.begin() ; iter != str.end() ; ++iter) - *iter = tolower(*iter); - } - - protected: - vector > fetchValuesFromIndex(size_t start, const char* key) - { - vector > result; - for (size_t index = start ; index < m_index; index++) { - KVPair* entry = m_data + index; - - if (compareString(entry->key, key) <= 0) { - result.push_back(pair(entry->key, entry->value)); - } - else { - break; - } - } - - return result; - } - - size_t findFirstOccuranceOfKey(const char* key, bool closest = false) - { - if (!m_index) - return m_index; - - size_t mid, low = 0, high = m_index - 1; - - while (low <= high) { - mid = (low + high) / 2; - - char* entryKey = (m_data + mid)->key; - int cmp = compareString(key, entryKey); - - if (!cmp) { - if (!mid) - return mid; - - size_t oneUp = mid - 1; - if (!compareString(key, (m_data + oneUp)->key)) - high = oneUp; - else - return mid; - } - else { - if (closest) { - if (mid > 0) { - if (compareString(key, (m_data + mid - 1)->key) > 0 && compareString(key, entryKey) <= 0) - return mid; - } - } - - if (cmp < 0) { - if (!mid) { - if (closest) - return 0; - - return m_index; - } - - high = mid - 1; - } - else { - if (low + 1 >= m_index) - return m_index; - - low = mid + 1; - } - } - } - - return m_index; - } - - int compareString(const char* a, const char* b) - { - #ifndef WIN32 - return m_caseSensitive ? strcmp(a, b) : strcasecmp(a, b); - #else - return m_caseSensitive ? strcmp(a, b) : _stricmp(a, b); - #endif - } - - static int qsortCompare(const void* a, const void* b) - { - int cmp; - char* aa = ((const KVPair*)a)->key; - char* bb = ((const KVPair*)b)->key; - - #ifndef WIN32 - if (!(cmp = strcasecmp(aa, bb))) - #else - if (!(cmp = _stricmp(aa, bb))) - #endif - return aa == bb ? 0 : (aa > bb ? 1 : -1); - else - return cmp; - } - - static int qsortCompareCaseSensitive(const void* a, const void* b) - { - int cmp; - char* aa = ((const KVPair*)a)->key; - char* bb = ((const KVPair*)b)->key; - - if (!(cmp = strcmp(aa, bb))) - return aa == bb ? 0 : (aa > bb ? 1 : -1); - else - return cmp; - } - - - void grow() - { - size_t growSize = m_growSize ? m_growSize : m_size; - KVPair* newData = (KVPair*)malloc(sizeof(KVPair) * (m_size + growSize)); - memcpy(newData, m_data, sizeof(KVPair) * m_size); - memset(newData + m_size, 0, sizeof(KVPair) * growSize); - - KVPair* tmp = m_data; - m_data = newData; - free(tmp); - - m_size += growSize; - } - - protected: - struct KVPair { - char* key; - char* value; - }; - - bool m_caseSensitive; - size_t m_growSize; - size_t m_size; - size_t m_index; - KVPair* m_data; - }; - - class OVCINDataTable { - public: - ~OVCINDataTable() - { - if (m_propertyMap) - delete m_propertyMap; - if (m_keynameMap) - delete m_keynameMap; - if (m_chardefMap) - delete m_chardefMap; - if (m_data) - free (m_data); - } - - string findProperty(const string& key) - { - vector > result = m_propertyMap->findPairsWithKey(key.c_str()); - if (result.size()) return result[0].second; - return string(); - } - - string findKeyname(const string& key) - { - vector > result = m_keynameMap->findPairsWithKey(key.c_str()); - if (result.size()) return result[0].second; - return string(); - } - - vector findChardef(const string& key) - { - vector > ret = m_chardefMap->findPairsWithKey(key.c_str()); - vector result; - vector >::iterator iter= ret.begin(); - - for ( ; iter != ret.end(); iter++) - result.push_back((*iter).second); - - return result; - } - - vector > findChardefWithWildcard(const OVWildcard& wildcard) - { - return m_chardefMap->findPairsWithWildcard(wildcard); - } - - OVFastKeyValuePairMap* propertyMap() - { - return m_propertyMap; - } - - OVFastKeyValuePairMap* keynameMap() - { - return m_keynameMap; - } - - OVFastKeyValuePairMap* chardefMap() - { - return m_chardefMap; - } - - protected: - friend class OVCINDataTableParser; - OVCINDataTable(char* data, OVFastKeyValuePairMap* propertyMap, OVFastKeyValuePairMap* keynameMap, OVFastKeyValuePairMap* chardefMap) - : m_data(data) - , m_propertyMap(propertyMap) - , m_keynameMap(keynameMap) - , m_chardefMap(chardefMap) - { - } - - char* m_data; - OVFastKeyValuePairMap* m_propertyMap; - OVFastKeyValuePairMap* m_keynameMap; - OVFastKeyValuePairMap* m_chardefMap; - }; - - - - class OVCINDataTableParser { - public: - enum { - NoFileError, - SeekError, - EmptyFileError, - MemoryAllocationError, - ReadError, - NoDataError = EmptyFileError - }; - - OVCINDataTableParser() - : m_data(0) - , m_lastError(0) - { - } - - ~OVCINDataTableParser() - { - if (m_data) - free(m_data); - } - - int lastError() - { - return m_lastError; - } - - OVCINDataTable* CINDataTableFromFileName(const string& filename, bool caseSensitive = false) - { - if (m_data) { - free(m_data); - m_data = 0; - } - - FILE* f = OVFileHelper::OpenStream(filename); - if (!f) { - m_lastError = NoFileError; - return 0; - } - - OVCINDataTable* table; - OVCINDataTableParser parser; - table = parser.CINDataTableFromFileStream(f, caseSensitive); - fclose(f); - return table; - } - - OVCINDataTable* CINDataTableFromString(const char* string, bool caseSensitive = false) - { - if (m_data) { - free(m_data); - m_data = 0; - } - - size_t len = strlen(string); - if (!len) { - m_lastError = NoDataError; - return 0; - } - - m_data = (char*)calloc(1, len + 1); - if (!m_data) { - m_lastError = MemoryAllocationError; - return 0; - } - - memcpy(m_data, string, len); - - return CINDataTableFromRetainedData(caseSensitive); - } - - - OVCINDataTable* CINDataTableFromFileStream(FILE* stream, bool caseSensitive = false) - { - if (m_data) { - free(m_data); - m_data = 0; - } - - if (!stream) { - m_lastError = NoFileError; - return 0; - } - - if (fseek(stream, 0, SEEK_END) == -1) { - m_lastError = SeekError; - return 0; - } - - size_t size; - if (!(size = ftell(stream))) { - m_lastError = EmptyFileError; - return 0; - } - - if (fseek(stream, 0, SEEK_SET) == -1) { - m_lastError = SeekError; - return 0; - } - - m_data = (char*)calloc(1, size + 1); - if (!m_data) { - m_lastError = MemoryAllocationError; - return 0; - } - - if (fread(m_data, 1, size, stream) != size) { - m_lastError = ReadError; - return 0; - } - - return CINDataTableFromRetainedData(caseSensitive); - } - - protected: - OVCINDataTable* CINDataTableFromRetainedData(bool caseSensitive) - { - OVFastKeyValuePairMap* propertyMap = new OVFastKeyValuePairMap(16, 0, caseSensitive); - if (!propertyMap) { - free(m_data); - m_lastError = MemoryAllocationError; - return 0; - } - - OVFastKeyValuePairMap* keynameMap = new OVFastKeyValuePairMap(64, 0, caseSensitive); - if (!keynameMap) { - free(m_data); - delete propertyMap; - m_lastError = MemoryAllocationError; - return 0; - } - - OVFastKeyValuePairMap* chardefMap = new OVFastKeyValuePairMap(1024, 0, caseSensitive); - if (!chardefMap) { - free(m_data); - delete propertyMap; - delete keynameMap; - m_lastError = MemoryAllocationError; - return 0; - } - - m_scanner = m_data; - - char first; - int blockMode = 0; - - while (first = *m_scanner) { - if (blockMode) { - if (first == '\r' || first == '\n') { - m_scanner++; - continue; - } - - char* key = m_scanner; - char* value = const_cast(""); - char endingChar = skipToSpaceCharOrLineEndAndMarkAndForward(); - - if (endingChar == '\r' || endingChar == '\n') { - ; - } - else { - endingChar = skipUntilNonSpaceChar(); - if (endingChar == '\r' || endingChar == '\n') { - ; - } - else { - value = m_scanner; - skipToLineEndAndMarkAndForwardWithoutTrailingSpace(); - } - } - - if (blockMode == 1) { - if (!strcmp(key, "%keyname") && !strcmp(value, "end")) { - blockMode = 0; - } - else { - if (!caseSensitive) - makeLowerCase(key); - - keynameMap->add(key, value); - } - } - else if (blockMode == 2) { - if (!strcmp(key, "%chardef") && !strcmp(value, "end")) { - blockMode = 0; - } - - else { - if (!caseSensitive) - makeLowerCase(key); - - chardefMap->add(key, value); - } - } - - continue; - } - - - if (first == '#') { - skipUntilNextLine(); - continue; - } - - if (first == '%') { - m_scanner++; - - char* key; - char* value = const_cast(""); - - if (*(key = m_scanner)) { - char endingChar = skipToSpaceCharOrLineEndAndMarkAndForward(); - - if (endingChar == '\r' || endingChar == '\n') { - ; - } - else { - endingChar = skipUntilNonSpaceChar(); - if (endingChar == '\r' || endingChar == '\n') { - ; - } - else { - value = m_scanner; - skipToLineEndAndMarkAndForwardWithoutTrailingSpace(); - } - } - - if (!strcmp(key, "keyname") && !strcmp(value, "begin")) { - blockMode = 1; - } - else if (!strcmp(key, "chardef") && !strcmp(value, "begin")) { - blockMode = 2; - } - else { - propertyMap->add(key, value); - } - } - - continue; - } - - m_scanner++; - } - - propertyMap->sortAndFreeze(); - keynameMap->sortAndFreeze(); - chardefMap->sortAndFreeze(); - - OVCINDataTable* table = new OVCINDataTable(m_data, propertyMap, keynameMap, chardefMap); - - if (!table) { - free(m_data); - delete propertyMap; - delete keynameMap; - delete chardefMap; - m_lastError = MemoryAllocationError; - return 0; - } - else { - m_data = 0; - } - - return table; - } - - void skipUntilNextLine() - { - skipUntilEitherCRLF(); - skipUntilNeitherCRLF(); - } - - void skipUntilEitherCRLF() - { - char nextChar; - while (nextChar = *m_scanner) { - if (nextChar == '\r' || nextChar == '\n') break; - m_scanner++; - } - } - - void skipUntilNeitherCRLF() - { - char nextChar; - while (nextChar = *m_scanner) { - if (!(nextChar == '\r' || nextChar == '\n')) break; - m_scanner++; - } - } - - void skipToLineEndAndMarkAndForwardWithoutTrailingSpace() - { - char nextChar; - while (nextChar = *m_scanner) { - if (nextChar == ' ' || nextChar == '\t') { - char* begin = m_scanner; - - m_scanner++; - while (nextChar = *m_scanner) { - if (!(nextChar == ' ' || nextChar == '\t')) break; - m_scanner++; - } - - if (nextChar == '\n' || nextChar == '\r') { - *begin = 0; - m_scanner++; - return; - } - else { - m_scanner++; - continue; - } - } - - if (nextChar == '\n' || nextChar == '\r') { - *m_scanner = 0; - m_scanner++; - return; - } - - m_scanner++; - } - } - - char skipToSpaceCharOrLineEndAndMarkAndForward() - { - char nextChar; - while (nextChar = *m_scanner) { - if (nextChar == ' ' || nextChar == '\t' || nextChar == '\n' || nextChar == '\r') { - *m_scanner = 0; - m_scanner++; - return nextChar; - } - - m_scanner++; - } - - return 0; - } - - char skipUntilNonSpaceChar() - { - char nextChar; - while (nextChar = *m_scanner) { - if (!(nextChar == ' ' || nextChar == '\t')) - return nextChar; - - m_scanner++; - } - - return 0; - } - - void makeLowerCase(char* ptr) - { - while (*ptr) { - *ptr = tolower(*ptr); - ptr++; - } - } - - protected: - char* m_data; - char* m_scanner; - int m_lastError; - - public: - static const map QuickParseProperty(const string& filename) - { - map properties; - FILE* stream = OVFileHelper::OpenStream(filename); - - if (!stream) - return properties; - - while (!feof(stream)) { - char buffer[256]; - fgets(buffer, sizeof(buffer) - 1, stream); - - if (!*buffer) - continue; - - if (*buffer == '#') - continue; - - pair pv = SplitPropertyString(buffer + 1); - if (pv.first == "keyname") - break; - - properties[pv.first] = pv.second; - } - - fclose(stream); - return properties; - } - - static pair SplitPropertyString(const char* str) - { - const char* scanner = str; - while (*scanner) { - if (*scanner == ' ' || *scanner == '\t' || *scanner == '\r' || *scanner == '\n') - break; - scanner++; - } - - string property = string(str, (size_t)(scanner - str)); - - while (*scanner) { - if (*scanner != ' ' && *scanner != '\t') - break; - scanner++; - } - - const char* begin = scanner; - while (*scanner) { - if (*scanner == '\r' || *scanner == '\n') - break; - scanner++; - } - - string value = string(begin, (size_t)(scanner - begin)); - return pair(property, value); - } - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVCINDatabaseService.h b/Source/3rdParty/OpenVanilla/OVCINDatabaseService.h deleted file mode 100644 index 0112319e9..000000000 --- a/Source/3rdParty/OpenVanilla/OVCINDatabaseService.h +++ /dev/null @@ -1,169 +0,0 @@ -/* - * OVCINDatabaseService.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVCINDatabaseService_h -#define OVCINDatabaseService_h - -#if defined(__APPLE__) - #include - #include -#else - #include "OVCINDataTable.h" - #include "OVDatabaseService.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVCINDatabaseService; - - class OVCINKeyValueDataTable : public OVKeyValueDataTableInterface - { - public: - ~OVCINKeyValueDataTable() - { - delete m_table; - } - - virtual const vector valuesForKey(const string& key) - { - return m_table->findChardef(key); - } - - virtual const vector > valuesForKey(const OVWildcard& expression) - { - return m_table->findChardefWithWildcard(expression); - } - - virtual const string valueForProperty(const string& property) - { - if (OVKeynamePropertyHelper::IsPropertyKeyname(property)) - return m_table->findKeyname(OVKeynamePropertyHelper::KeynameFromProperty(property)); - else - return m_table->findProperty(property); - } - - protected: - OVCINDataTable* m_table; - - friend class OVCINDatabaseService; - - OVCINKeyValueDataTable(OVCINDataTable* table) - : m_table(table) - { - } - }; - - - class OVCINDatabaseService : public OVDatabaseService { - public: - OVCINDatabaseService() - { - } - - OVCINDatabaseService(const string& pathToScan, const string& includePattern = "*.cin", const string& excludePattern = "", size_t depth = 1) - { - addDirectory(pathToScan, includePattern, excludePattern, depth); - } - - // note addDirectory overwrites the table data, so scan user directory after scan the systems if you want to give precedence to user' tables - void addDirectory(const string& pathToScan, const string& includePattern = "*.cin", const string& excludePattern = "", size_t depth = 1) - { - string pathPrefix = OVPathHelper::NormalizeByExpandingTilde(pathToScan) + OVPathHelper::Separator(); - size_t prefixLength = pathPrefix.length(); - - vector tables = OVDirectoryHelper::Glob(pathPrefix, includePattern, excludePattern, depth); - - vector::iterator iter = tables.begin(); - for ( ; iter != tables.end(); ++iter) { - const string& path = *iter; - string shortPath = *iter; - shortPath.erase(0, prefixLength); - - string tableName = OVCINDatabaseService::TableNameFromPath(shortPath); - m_tables[tableName] = path; - } - } - - virtual const vector tables(const OVWildcard& filter = string("*")) - { - vector result; - - for (map::iterator iter = m_tables.begin() ; iter != m_tables.end(); ++iter) { - if (filter.match((*iter).first)) - result.push_back((*iter).first); - } - - return result; - } - - virtual bool tableSupportsValueToKeyLookup(const string &tableName) - { - return false; - } - - virtual OVKeyValueDataTableInterface* createKeyValueDataTableInterface(const string& name, bool suggestedCaseSensitivity = false) - { - map::iterator iter = m_tables.find(name); - if (iter == m_tables.end()) - return 0; - - OVCINDataTableParser parser; - OVCINDataTable* table = parser.CINDataTableFromFileName((*iter).second, suggestedCaseSensitivity); - - if (!table) - return 0; - - return new OVCINKeyValueDataTable(table); - } - - - virtual const string valueForPropertyInTable(const string& property, const string& name) - { - map::iterator iter; - - if (name != m_cachedTableName) - { - iter = m_tables.find(name); - if (iter == m_tables.end()) - return string(); - - m_cachedProperties = OVCINDataTableParser::QuickParseProperty((*iter).second); - m_cachedTableName = name; - } - - iter = m_cachedProperties.find(property); - if (iter != m_cachedProperties.end()) - return (*iter).second; - - return string(); - } - - protected: - map m_tables; - map m_cachedProperties; - string m_cachedTableName; - - public: - static const string TableNameFromPath(const string& path) - { - string result; - char separator = OVPathHelper::Separator(); - - string::const_iterator iter = path.begin(); - for ( ; iter != path.end(); ++iter) - if (*iter == separator || *iter == '.') - result += '-'; - else - result += *iter; - - return result; - } - }; -}; - -#endif \ No newline at end of file diff --git a/Source/3rdParty/OpenVanilla/OVCINToSQLiteConvertor.h b/Source/3rdParty/OpenVanilla/OVCINToSQLiteConvertor.h deleted file mode 100644 index 3a5112aa2..000000000 --- a/Source/3rdParty/OpenVanilla/OVCINToSQLiteConvertor.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * OVCINToSQLiteConvertor.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVCINToSQLiteConvertor_h -#define OVCINToSQLiteConvertor_h - -#if defined(__APPLE__) - #include - #include -#else - #include "OVCINDataTable.h" - #include "OVSQLiteWrapper.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVCINToSQLiteConvertor { - protected: - static bool InsertKeyValue(OVFastKeyValuePairMap* map, OVSQLiteStatement* statement, const char* prefix = 0) - { - string prefixString = prefix ? prefix : ""; - - size_t size = map->size(); - - for (size_t index = 0; index < size; index++) { - pair kvpair = map->keyValuePairAtIndex(index); - - const string& key = prefix ? prefixString + kvpair.first : kvpair.first; - - statement->bindTextToColumn(key, 1); - statement->bindTextToColumn(kvpair.second, 2); - - if (statement->step() != SQLITE_DONE) - return false; - - statement->reset(); - } - - return true; - } - - public: - static bool Convert(OVCINDataTable* table, OVSQLiteConnection* connection, const string& tableName, bool overwriteTable = true) - { - const char* nameStr = tableName.c_str(); - // query if the table exists - OVSQLiteStatement* statement; - - statement = connection->prepare("SELECT name FROM sqlite_master WHERE name = %Q", nameStr); - - if (!statement) - return false; - - if (statement->step() == SQLITE_ROW && overwriteTable) - { - delete statement; - - if (connection->execute("DROP TABLE %Q", nameStr) != SQLITE_OK) { - delete statement; - return false; - } - } - else - delete statement; - - if (connection->execute("CREATE TABLE %Q (key, value)", nameStr) != SQLITE_OK) - return false; - - string indexName = tableName + "_index"; - if (connection->execute("CREATE INDEX %Q on %Q (key)", indexName.c_str(), nameStr) != SQLITE_OK) - return false; - - statement = connection->prepare("INSERT INTO %Q VALUES (?, ?)", nameStr); - if (!statement) { - return false; - } - - if (connection->execute("BEGIN") != SQLITE_OK) { - return false; - } - - string keynameProperty = string(OVPropertyStringInternalPrefix) + string(OVCINKeynameString); - - if (InsertKeyValue(table->propertyMap(), statement, OVPropertyStringInternalPrefix)) - if (InsertKeyValue(table->keynameMap(), statement, keynameProperty.c_str())) - InsertKeyValue(table->chardefMap(), statement); - - delete statement; - - if (connection->execute("COMMIT") != SQLITE_OK) { - return false; - } - - return true; - } - - }; -}; - -#endif \ No newline at end of file diff --git a/Source/3rdParty/OpenVanilla/OVCandidateService.h b/Source/3rdParty/OpenVanilla/OVCandidateService.h deleted file mode 100644 index 9a8d08e30..000000000 --- a/Source/3rdParty/OpenVanilla/OVCandidateService.h +++ /dev/null @@ -1,188 +0,0 @@ -/* - * OVCandidateService.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVCandidateService_h -#define OVCandidateService_h - -#if defined(__APPLE__) - #include - #include - #include -#else - #include "OVBase.h" - #include "OVKey.h" - #include "OVLoaderService.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVCandidateList : public OVBase { - public: - virtual void clear() = 0; - virtual size_t size() const = 0; - virtual string candidateAtIndex(size_t index) const = 0; - virtual void setCandidate(size_t index, const string& candidate) = 0; - virtual void setCandidates(const vector& candidates) = 0; - virtual void addCandidate(const string& candidate) = 0; - virtual void addCandidates(const vector& candidates) = 0; - }; - - class OVCandidatePanel; - class OVOneDimensionalCandidatePanel; - // class OVTwoDimensionalCandidatePanel; - class OVFreeContentPanel; - class OVPlainTextCandidatePanel; - class OVHTMLCandidatePanel; - - class OVCandidatePanel : public OVBase { - public: - virtual bool isOneDimensionalPanel() const - { - return false; - } - - virtual bool isTwoDimensionalPanel() const - { - return false; - } - - virtual bool isPlainTextPanelPanel() const - { - return false; - } - - virtual bool isHTMLPanel() const - { - return false; - } - - virtual void hide() = 0; - virtual void show() = 0; - virtual void updateDisplay() = 0; - virtual bool isVisible() = 0; - - virtual void setPrompt(const string& prompt) = 0; - virtual string prompt() = 0; - - virtual bool yieldToCandidateEventHandler() = 0; - virtual void cancelEventHandler() = 0; - - virtual void reset() = 0; - }; - - class OVOneDimensionalCandidatePanel : public OVCandidatePanel { - public: - virtual bool isOneDimensionalPanel() const - { - return true; - } - - virtual bool isHorizontal() const = 0; - virtual bool isVertical() const = 0; - - virtual OVCandidateList* candidateList() = 0; - - virtual size_t candidatesPerPage() const = 0; - virtual void setCandidatesPerPage(size_t number) = 0; - virtual size_t pageCount() const = 0; - virtual size_t currentPage() const = 0; - virtual size_t currentPageCandidateCount() const = 0; - virtual bool allowsPageWrapping() const = 0; - virtual void setAllowsPageWrapping(bool allowsPageWrapping) = 0; - - virtual size_t currentHightlightIndex() const = 0; - virtual void setHighlightIndex(size_t index) = 0; - virtual size_t currentHightlightIndexInCandidateList() const = 0; - - virtual size_t goToNextPage() = 0; - virtual size_t goToPreviousPage() = 0; - virtual size_t goToPage(size_t page) = 0; - - virtual const OVKey candidateKeyAtIndex(size_t index) = 0; - virtual void setCandidateKeys(const string& asciiKeys, OVLoaderService* loaderService) - { - OVKeyVector keys; - for (size_t index = 0; index < asciiKeys.length(); index++) { - keys.push_back(loaderService->makeOVKey(asciiKeys[index])); - } - - setCandidateKeys(keys); - setCandidatesPerPage(asciiKeys.length()); - } - - virtual void setCandidateKeys(const OVKeyVector& keys) = 0; - virtual void setNextPageKeys(const OVKeyVector& keys) = 0; - virtual void setPreviousPageKeys(const OVKeyVector& keys) = 0; - virtual void setNextCandidateKeys(const OVKeyVector& keys) = 0; - virtual void setPreviousCandidateKeys(const OVKeyVector& keys) = 0; - virtual void setCancelKeys(const OVKeyVector& keys) = 0; - virtual void setChooseHighlightedCandidateKeys(const OVKeyVector& keys) = 0; - - virtual const OVKeyVector defaultCandidateKeys() const = 0; - virtual const OVKeyVector defaultNextPageKeys() const = 0; - virtual const OVKeyVector defaultNextCandidateKeys() const = 0; - virtual const OVKeyVector defaultPreviousPageKeys() const = 0; - virtual const OVKeyVector defaultPreviousCandidateKeys() const = 0; - virtual const OVKeyVector defaultCancelKeys() const = 0; - virtual const OVKeyVector defaultChooseHighlightedCandidateKeys() const = 0; - }; - - class OVFreeContentStorage : public OVBase { - public: - virtual void clear() = 0; - virtual void setContent(const string& content) = 0; - virtual void appendContent(const string& content) = 0; - }; - - class OVPlainTextCandidatePanel : public OVCandidatePanel { - public: - virtual bool isPlainTextPanelPanel() - { - return true; - } - - virtual OVFreeContentStorage* textStorage() = 0; - }; - - class OVHTMLCandidatePanel : public OVCandidatePanel { - public: - virtual OVFreeContentStorage* HTMLSourceStorage() = 0; - }; - - class OVCandidateService : public OVBase { - public: - virtual OVOneDimensionalCandidatePanel* useHorizontalCandidatePanel() - { - return 0; - } - - virtual OVOneDimensionalCandidatePanel* useVerticalCandidatePanel() - { - return 0; - } - - virtual OVOneDimensionalCandidatePanel* useOneDimensionalCandidatePanel() - { - return useVerticalCandidatePanel(); - } - - // virtual OVTwoDimensionalCandidatePanel* twoDimensionalCandidatePanel(); - - virtual OVPlainTextCandidatePanel* usePlainTextCandidatePanel() - { - return 0; - } - - virtual OVHTMLCandidatePanel* useHTMLCandidatePanel() - { - return 0; - } - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVDatabaseService.h b/Source/3rdParty/OpenVanilla/OVDatabaseService.h deleted file mode 100644 index 7455f5411..000000000 --- a/Source/3rdParty/OpenVanilla/OVDatabaseService.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * OVDatabaseService.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVDatabaseService_h -#define OVDatabaseService_h - -#if defined(__APPLE__) - #include - #include - #include - #include -#else - #include "OVBase.h" - #include "OVCINDataTable.h" - #include "OVFileHelper.h" - #include "OVWildcard.h" -#endif - -#include - -namespace OpenVanilla { - using namespace std; - - // database-backed table uses this prefix to store properties ina key-value data table - #define OVPropertyStringInternalPrefix "__property_" - - #define OVCINKeynameString "keyname-" - #define OVCINKeynameStringLength 8 - - // keyname is defined in the .cin format, not to be confused with the key in key-value pairs - class OVKeynamePropertyHelper : public OVBase { - public: - static bool IsPropertyKeyname(const string& property) - { - return (property.substr(0, OVCINKeynameStringLength) == OVCINKeynameString); - } - - // passes property and gets back the keyname - static const string KeynameFromProperty(const string& property) - { - return IsPropertyKeyname(property) ? property.substr(OVCINKeynameStringLength, property.length() - OVCINKeynameStringLength) : string(); - } - - // passes keyname and gets the combined property - static const string KeynameToProperty(const string& keyname) - { - return string(OVCINKeynameString) + keyname; - } - }; - - class OVKeyValueDataTableInterface : public OVBase { - public: - virtual const vector valuesForKey(const string& key) = 0; - virtual const vector > valuesForKey(const OVWildcard& expression) = 0; - virtual const string valueForProperty(const string& property) = 0; - - // only supported by database services that support value-to-key lookup, the default implementation is an empty vector - virtual const vector keysForValue(const string& value) - { - return vector(); - } - }; - - class OVDatabaseService : public OVBase { - public: - virtual const vector tables(const OVWildcard& filter = string("*")) = 0; - virtual bool tableSupportsValueToKeyLookup(const string &tableName) = 0; - - virtual OVKeyValueDataTableInterface* createKeyValueDataTableInterface(const string& name, bool suggestedCaseSensitivity = false) = 0; - - // this is needed so that modules like OVIMGeneric can know table localized names in advance, without really loading them - virtual const string valueForPropertyInTable(const string& property, const string& name) = 0; - }; - -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVDateTimeHelper.h b/Source/3rdParty/OpenVanilla/OVDateTimeHelper.h deleted file mode 100644 index ea140709e..000000000 --- a/Source/3rdParty/OpenVanilla/OVDateTimeHelper.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * OVDateTimeHelper.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVDateTimeHelper_h -#define OVDateTimeHelper_h - -#include -#include -#include - -namespace OpenVanilla { - using namespace std; - - class OVDateTimeHelper - { - public: - static time_t GetTimeIntervalSince1970() - { - return time(NULL); - } - - static time_t GetTimeIntervalSince1970FromString(const string& s) - { - stringstream sst; - sst << s; - time_t t; - sst >> t; - return t; - } - - static const string GetTimeIntervalSince1970AsString() - { - stringstream sst; - sst << time(NULL); - return sst.str(); - } - - static time_t GetTimeIntervalSince1970AtBeginningOfTodayLocalTime() - { - time_t t = time(NULL); - - #ifdef WIN32 - struct tm tdata; - struct tm* td = &tdata; - if (localtime_s(td, &t)) - return 0; - #else - struct tm* td; - td = localtime(&t); - #endif - - td->tm_hour = 0; - td->tm_min = 0; - td->tm_sec = 0; - - return mktime(td); - } - - static const string LocalTimeString() - { - time_t t = time(NULL); - - #ifdef WIN32 - struct tm tdata; - struct tm* td = &tdata; - if (localtime_s(td, &t)) - return string(); - #else - struct tm* td; - td = localtime(&t); - #endif - - ostringstream sstr; - sstr.width(2); - sstr.fill('0'); - sstr << td->tm_hour << ":"; - sstr.width(2); - sstr.fill('0'); - sstr << td->tm_min << ":"; - sstr.width(2); - sstr.fill('0'); - sstr << td->tm_sec; - return sstr.str(); - } - - static const string LocalDateTimeString() - { - time_t t = time(NULL); - - #ifdef WIN32 - struct tm tdata; - struct tm* td = &tdata; - if (localtime_s(td, &t)) - return string(); - #else - struct tm* td; - td = localtime(&t); - #endif - - ostringstream sstr; - sstr.width(4); - sstr << td->tm_year + 1900 << "-"; - sstr.width(2); - sstr.fill('0'); - sstr << td->tm_mon + 1 << "-"; - sstr.width(2); - sstr.fill('0'); - sstr << td->tm_mday << " "; - - sstr.width(2); - sstr.fill('0'); - sstr << td->tm_hour << ":"; - sstr.width(2); - sstr.fill('0'); - sstr << td->tm_min << ":"; - sstr.width(2); - sstr.fill('0'); - sstr << td->tm_sec; - return sstr.str(); - } - }; - -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVEncodingService.h b/Source/3rdParty/OpenVanilla/OVEncodingService.h deleted file mode 100644 index 518c98086..000000000 --- a/Source/3rdParty/OpenVanilla/OVEncodingService.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * OVEncodingService.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVEncodingService_h -#define OVEncodingService_h - -#if defined(__APPLE__) - #include -#else - #include "OVUTF8Helper.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVEncodingService : public OVBase { - public: - virtual bool stringSupportedByEncoding(const string& text, const string& encoding) - { - vector svec = OVUTF8Helper::SplitStringByCodePoint(text); - for (vector::iterator iter = svec.begin() ; iter != svec.end() ; ++iter) - if (!codepointSupportedByEncoding(*iter, encoding)) - return false; - - return true; - } - - virtual bool stringSupportedBySystem(const string& text) - { - vector svec = OVUTF8Helper::SplitStringByCodePoint(text); - for (vector::iterator iter = svec.begin() ; iter != svec.end() ; ++iter) - if (!codepointSupportedBySystem(*iter)) - return false; - - return true; - } - - virtual bool codepointSupportedByEncoding(const string& codepoint, const string& encoding) = 0; - virtual bool codepointSupportedBySystem(const string& codepoint) = 0; - virtual const vector supportedEncodings() = 0; - virtual bool isEncodingSupported(const string& encoding) = 0; - - virtual bool isEncodingConversionSupported(const string& fromEncoding, const string& toEncoding) = 0; - virtual const pair convertEncoding(const string& fromEncoding, const string& toEncoding, const string& text) = 0; - }; -}; - -#endif \ No newline at end of file diff --git a/Source/3rdParty/OpenVanilla/OVEventHandlingContext.h b/Source/3rdParty/OpenVanilla/OVEventHandlingContext.h deleted file mode 100644 index 54c7c6fc9..000000000 --- a/Source/3rdParty/OpenVanilla/OVEventHandlingContext.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * OVEventHandlingContext.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVEventHandlingContext_h -#define OVEventHandlingContext_h - -#if defined(__APPLE__) - #include - #include - #include - #include - #include - #include -#else - #include "OVBase.h" - #include "OVCandidateService.h" - #include "OVStringHelper.h" - #include "OVTextBuffer.h" - #include "OVKey.h" - #include "OVLoaderService.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVEventHandlingContext : public OVBase { - public: - virtual void startSession(OVLoaderService* loaderService) - { - } - - virtual void stopSession(OVLoaderService* loaderService) - { - } - - virtual void clear(OVLoaderService* loaderService) - { - stopSession(loaderService); - startSession(loaderService); - } - - virtual bool handleKey(OVKey* key, OVTextBuffer* readingText, OVTextBuffer* composingText, OVCandidateService* candidateService, OVLoaderService* loaderService) - { - return false; - } - - virtual bool handleDirectText(const vector& segments, OVTextBuffer* readingText, OVTextBuffer* composingText, OVCandidateService* candidateService, OVLoaderService* loaderService) - { - return handleDirectText(OVStringHelper::Join(segments), readingText, composingText, candidateService, loaderService); - } - - virtual bool handleDirectText(const string&, OVTextBuffer* readingText, OVTextBuffer* composingText, OVCandidateService* candidateService, OVLoaderService* loaderService) - { - return false; - } - - virtual void candidateCanceled(OVCandidateService* candidateService, OVTextBuffer* readingText, OVTextBuffer* composingText, OVLoaderService* loaderService) - { - } - - virtual bool candidateSelected(OVCandidateService* candidateService, const string& text, size_t index, OVTextBuffer* readingText, OVTextBuffer* composingText, OVLoaderService* loaderService) - { - return true; - } - - virtual bool candidateNonPanelKeyReceived(OVCandidateService* candidateService, const OVKey* key, OVTextBuffer* readingText, OVTextBuffer* composingText, OVLoaderService* loaderService) - { - return false; - } - - virtual const string filterText(const string& inputText, OVLoaderService* loaderService) - { - return inputText; - } - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVException.h b/Source/3rdParty/OpenVanilla/OVException.h deleted file mode 100644 index 536b3363f..000000000 --- a/Source/3rdParty/OpenVanilla/OVException.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * OVException.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVException_h -#define OVException_h - -#if defined(__APPLE__) - #include -#else - #include "OVBase.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVException { - public: - class OverflowException {}; - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVFileHelper.h b/Source/3rdParty/OpenVanilla/OVFileHelper.h deleted file mode 100644 index a630caf68..000000000 --- a/Source/3rdParty/OpenVanilla/OVFileHelper.h +++ /dev/null @@ -1,607 +0,0 @@ -/* - * OVFileHelper.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVFileHelper_h -#define OVFileHelper_h - -#if defined(__APPLE__) - #include - #include -#else - #include - #include - #include -#endif - -#include - -#include -#include -#include -#include "OVWildcard.h" -#include "OVUTF8Helper.h" - -namespace OpenVanilla { - using namespace std; - - class OVFileHelper { - public: - static FILE* OpenStream(const string& filename, const string& mode = "rb") - { - #ifndef WIN32 - return fopen(filename.c_str(), mode.c_str()); - #else - FILE* stream = NULL; - errno_t err = _wfopen_s(&stream, OVUTF16::FromUTF8(filename).c_str(), OVUTF16::FromUTF8(mode).c_str()); - return err ? 0 : stream; - #endif - } - - static void OpenOFStream(ofstream& stream, const string& filename, ios_base::openmode openMode) - { - #ifndef WIN32 - stream.open(filename.c_str(), openMode); - #else - stream.open(OVUTF16::FromUTF8(filename).c_str(), openMode); - #endif - } - - static void OpenIFStream(ifstream& stream, const string& filename, ios_base::openmode openMode) - { - #ifndef WIN32 - stream.open(filename.c_str(), openMode); - #else - stream.open(OVUTF16::FromUTF8(filename).c_str(), openMode); - #endif - } - - // use free(), not delete[], to free the block allocated - static pair SlurpFile(const string& filename, bool addOneByteOfStringTerminationPadding = false) - { - FILE* stream = OpenStream(filename); - char* buf = 0; - size_t size = 0; - if (stream) { - if (!fseek(stream, 0, SEEK_END)) { - long lsize = ftell(stream); - if (lsize) { - size = (size_t)lsize; - if (!fseek(stream, 0, SEEK_SET)) { - buf = (char*)calloc(1, (size_t)size + (addOneByteOfStringTerminationPadding ? 1 : 0)); - if (buf) { - if (fread(buf, size, 1, stream) != 1) { - free(buf); - buf = 0; - size = 0; - } - } - else { - size = 0; - } - } - else { - size = 0; - } - } - } - - fclose(stream); - } - - return pair(buf, size); - } - }; - - class OVFileTimestamp { - public: - #if defined(__APPLE__) - OVFileTimestamp(__darwin_time_t timestamp = 0, long subtimestamp = 0) - #elif defined(WIN32) - OVFileTimestamp(time_t timestamp = 0, time_t subtimestamp = 0) - #else - #error We don't know about Linux yet, sorry. - #endif - : m_timestamp(timestamp) - , m_subtimestamp(subtimestamp) - { - } - - OVFileTimestamp(const OVFileTimestamp& timestamp) - : m_timestamp(timestamp.m_timestamp) - , m_subtimestamp(timestamp.m_subtimestamp) - { - } - - OVFileTimestamp& operator=(const OVFileTimestamp& timestamp) - { - m_timestamp = timestamp.m_timestamp; - m_subtimestamp = timestamp.m_subtimestamp; - return *this; - } - - - bool operator==(OVFileTimestamp& another) - { - return (m_timestamp == another.m_timestamp) && (m_subtimestamp == another.m_subtimestamp); - } - - bool operator!=(OVFileTimestamp& another) - { - return (m_timestamp != another.m_timestamp) || (m_subtimestamp != another.m_subtimestamp); - } - - bool operator<(OVFileTimestamp& another) - { - return (m_timestamp < another.m_timestamp) || ((m_timestamp == another.m_timestamp) && m_subtimestamp < another.m_subtimestamp); - } - - bool operator>(OVFileTimestamp& another) - { - return (m_timestamp > another.m_timestamp) || ((m_timestamp == another.m_timestamp) && m_subtimestamp > another.m_subtimestamp); - } - - - protected: - #if defined(__APPLE__) - __darwin_time_t m_timestamp; - long m_subtimestamp; - #elif defined(WIN32) - time_t m_timestamp; - time_t m_subtimestamp; - #else - #error We don't know about Linux yet, sorry. - #endif - }; - - - class OVPathHelper { - public: - static char Separator() - { - #ifndef WIN32 - return '/'; - #else - return '\\'; - #endif - } - - - static const string DirectoryFromPath(const string& path) - { - string realPath = OVPathHelper::NormalizeByExpandingTilde(path); - if (OVPathHelper::PathExists(realPath) && OVPathHelper::IsDirectory(realPath)) - return realPath; - - char separator = OVPathHelper::Separator(); - - if (!realPath.length()) - return string("."); - - for (size_t index = realPath.length() - 1; index >= 0 ; index--) { - if (realPath[index] == separator) { - if (index) { - // reserve the \\ on Windows - if (realPath[index-1] == '\\') - return realPath.substr(0, index + 1); - else - return realPath.substr(0, index); - } - else - return realPath.substr(0, 1); - } - - // if we run into : (like C:) on Windows - if (realPath[index] == ':') - return realPath.substr(0, index + 1) + Separator(); - - if (!index) break; - } - - return string("."); - } - - static const string FilenameWithoutPath(const string &path) - { - char separator = OVPathHelper::Separator(); - - if (!path.length()) - return string(); - - for (size_t index = path.length() - 1; index >= 0 ; index--) { - if (path[index] == separator) - return path.substr(index + 1, path.length() - (index + 1)); - - if (!index) break; - } - - return path; - } - - static const string FilenameWithoutExtension(const string &path) - { - char separator = OVPathHelper::Separator(); - if (!path.length()) - return string(); - - for (size_t index = path.length() - 1; index >= 0 ; index--) { - if (path[index] == separator) - break; - - if (path[index] == '.') - return path.substr(0, index); - - if (!index) break; - } - - return path; - } - - static const string ChopTrailingSeparator(const string& path) - { - if (path.length() == 1 && path[0] == Separator()) - return path; - - string result; - - if (path.length()) { - if (path[path.length() - 1] == Separator()) - result = path.substr(0, path.length() - 1); - else - result = path; - } - - return result; - } - - static const string ChopLeadingSeparator(const string& path) - { - if (path.length() == 1 && path[0] == Separator()) - return path; - - string result; - if (path.length()) { - if (path[0] == Separator()) { - result = path.substr(1, path.length() - 1); - } - else { - result = path; - } - } - return result; - } - - static const string PathCat(const string& s1, const string& s2) - { - if (s2.length()) - return ChopTrailingSeparator(s1) + Separator() + ChopLeadingSeparator(s2); - else - return s1; - } - - static const string Normalize(const string& path) - { - string newPath; - size_t length = path.length(); - for (size_t index = 0; index < length; index++) { - if (path[index] == '/' || path[index] == '\\') { - if (index < length - 1) { - if (path[index+1] == '/' || path[index+1] == '\\') - index++; - } - - newPath += Separator(); - } - else - newPath += path[index]; - } - - return ChopTrailingSeparator(newPath); - } - - static const bool PathExists(const string& path) - { - #ifndef WIN32 - struct stat buf; - return !stat(path.c_str(), &buf); - #else - struct _stat buf; - wstring wpath = OVUTF16::FromUTF8(path); - return !_wstat(wpath.c_str(), &buf); - #endif - } - - static const bool IsDirectory(const string& path) - { - #ifndef WIN32 - struct stat buf; - if (!stat(path.c_str(), &buf)) - { - if (buf.st_mode & S_IFDIR) - return true; - } - #else - struct _stat buf; - wstring wpath = OVUTF16::FromUTF8(path); - if (!_wstat(wpath.c_str(), &buf)) - { - if (buf.st_mode & S_IFDIR) - return true; - } - #endif - return false; - } - - static const OVFileTimestamp TimestampForPath(const string& path) - { - OVFileTimestamp timestamp; - #if defined(__APPLE__) - struct stat buf; - if (!stat(path.c_str(), &buf)) - { - timestamp = OVFileTimestamp(buf.st_mtimespec.tv_sec, buf.st_mtimespec.tv_nsec); - } - #elif defined(WIN32) - struct _stat buf; - wstring wpath = OVUTF16::FromUTF8(path); - if (!_wstat(wpath.c_str(), &buf)) - { - timestamp = OVFileTimestamp(buf.st_mtime); - } - #else - #error Sorry, no idea for Linux yet. - #endif - return timestamp; - } - - static bool RemoveEverythingAtPath(const string& path); - static const string NormalizeByExpandingTilde(const string& path); - }; - - class OVDirectoryHelper { - public: - static bool MakeDirectory(const string& path) - { - #ifndef WIN32 - return !mkdir(path.c_str(), S_IRWXU); - #else - wstring wpath = OVUTF16::FromUTF8(path); - return CreateDirectoryW(wpath.c_str(), NULL) == TRUE; - #endif - } - - static bool MakeDirectoryWithImmediates(const string& path) - { - string realPath = OVPathHelper::NormalizeByExpandingTilde(path); - - if (OVPathHelper::PathExists(realPath) && OVPathHelper::IsDirectory(realPath)) { - return true; - } - - string lastPart = OVPathHelper::DirectoryFromPath(realPath); - - if (lastPart != realPath && !OVPathHelper::PathExists(lastPart)) { - if (!MakeDirectoryWithImmediates(lastPart)) - return false; - } - - return MakeDirectory(realPath); - } - - static bool CheckDirectory(const string& path) - { - string realPath = OVPathHelper::NormalizeByExpandingTilde(path); - if (OVPathHelper::PathExists(realPath)) - return OVPathHelper::IsDirectory(realPath); - - return MakeDirectoryWithImmediates(realPath); - } - - static const vector Glob(const string& directory, const string& pattern = "*", const string& excludePattern = "", size_t depth = 1) - { - string sanitizedDirectory = OVPathHelper::NormalizeByExpandingTilde(directory); - - OVWildcard expression(pattern); - OVWildcard negativeExpression(excludePattern); - vector dirResult; - vector fileResult; - struct dirent** namelist = NULL; - - #ifndef WIN32 - int count = scandir(sanitizedDirectory.c_str(), &namelist, NULL, NULL); - for (int index = 0; index < count; index++) { - struct dirent* entry = namelist[index]; - bool isDir = entry->d_type == DT_DIR; - string name = entry->d_name; - #else - vector > foundFiles; - WIN32_FIND_DATAW findData; - HANDLE findHandle = FindFirstFileW(OVUTF16::FromUTF8(OVPathHelper::PathCat(sanitizedDirectory, "*.*")).c_str(), &findData); - if (findHandle != INVALID_HANDLE_VALUE) { - foundFiles.push_back(pair(OVUTF8::FromUTF16(findData.cFileName), (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)); - - while (FindNextFileW(findHandle, &findData)) - { - foundFiles.push_back(pair(OVUTF8::FromUTF16(findData.cFileName), (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)); - } - FindClose(findHandle); - } - - size_t count = foundFiles.size(); - for (size_t index = 0; index < count; index++) { - string name = foundFiles[index].first; - bool isDir = foundFiles[index].second; - #endif - - if ((!depth || depth > 1) && isDir && name != "." && name != "..") { - vector subResult = Glob(OVPathHelper::PathCat(sanitizedDirectory, name), pattern, excludePattern, depth ? depth - 1 : 0); - vector::iterator iter = subResult.begin(); - for ( ; iter != subResult.end() ; iter++) - dirResult.push_back(*iter); - } - - if (name != "." && name != ".." && expression.match(name) && !negativeExpression.match(name)) { - fileResult.push_back(OVPathHelper::PathCat(sanitizedDirectory, name)); - } - - #ifndef WIN32 - free(entry); - #endif - } - - #ifndef WIN32 - if (count != -1) - free(namelist); - #endif - - sort(dirResult.begin(), dirResult.end()); - sort(fileResult.begin(), fileResult.end()); - for (vector::iterator iter = dirResult.begin() ; iter != dirResult.end(); iter++) - fileResult.push_back(*iter); - - return fileResult; - } - - static const string TempDirectory() - { - #ifndef WIN32 - return string("/tmp"); - #else - WCHAR path[MAX_PATH + 1]; - GetTempPathW(MAX_PATH, path); - return OVUTF8::FromUTF16(path); - #endif - } - - static const string GenerateTempFilename(const string& prefix = "temp") - { - string pattern = OVPathHelper::PathCat(TempDirectory(), prefix + "-" + "XXXXXXXX"); - #ifndef WIN32 - char *p = (char*)calloc(1, pattern.length() + 1); - strcpy(p, pattern.c_str()); - char *r = mktemp(p); - if (!r) { - free(p); - return string(); - } - string result = r; - free(p); - return result; - #else - wstring wp = OVUTF16::FromUTF8(pattern); - size_t length = wp.length() + 1; - wchar_t* p = (wchar_t*)calloc(1, length * sizeof(wchar_t)); - wcscpy_s(p, length, wp.c_str()); - errno_t err = _wmktemp_s(p, length); - if (err != 0) { - free(p); - return string(); - } - - string result = OVUTF8::FromUTF16(p); - free(p); - return result; - - #endif - } - - static const string UserHomeDirectory() - { - #ifndef WIN32 - const char* homePath = getenv("HOME"); - if (homePath) - return string(homePath); - return TempDirectory(); - #else - WCHAR path[MAX_PATH + 1]; - HRESULT error = SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, SHGFP_TYPE_CURRENT, path); - if (!error) { - return OVUTF8::FromUTF16(path); - } - return TempDirectory(); - #endif - } - - static const string UserApplicationDataDirectory(const string& applicationName) - { - #if defined(__APPLE__) - return OVPathHelper::PathCat(UserHomeDirectory(), OVPathHelper::PathCat("/Library/", applicationName)); - #elif defined(WIN32) - WCHAR path[MAX_PATH + 1]; - HRESULT error = SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path); - if (!error) { - return OVPathHelper::PathCat(OVUTF8::FromUTF16(path), applicationName); - } - return OVPathHelper::PathCat(TempDirectory(), applicationName); - #else - return OVPathHelper::PathCat(UserHomeDirectory(), string(".") + applicationName); - #endif - } - - static const string UserApplicationSupportDataDirectory(const string& applicationName) - { - #ifdef __APPLE__ - return OVPathHelper::PathCat(UserHomeDirectory(), OVPathHelper::PathCat("/Library/Application Support", applicationName)); - #else - return UserApplicationDataDirectory(applicationName); - #endif - } - - static const string UserPreferencesDirectory(const string& applicationName) - { - #ifdef __APPLE__ - return OVPathHelper::NormalizeByExpandingTilde("~/Library/Preferences"); - #else - return UserApplicationDataDirectory(applicationName); - #endif - } - - }; - - inline const string OVPathHelper::NormalizeByExpandingTilde(const string& path) - { - string newPath = Normalize(path); - - if (newPath.length()) { - if (newPath[0] == '~') { - newPath = OVPathHelper::PathCat(OVDirectoryHelper::UserHomeDirectory(), newPath.substr(1, newPath.length())); - } - } - - return newPath; - } - - inline bool OVPathHelper::RemoveEverythingAtPath(const string& path) - { - if (!PathExists(path)) return false; - - if (IsDirectory(path)) { - vector files = OVDirectoryHelper::Glob(path, "*", "", 0); - vector::iterator iter = files.begin(); - for ( ; iter != files.end(); iter++) { - if (!RemoveEverythingAtPath(*iter)) - return false; - } - - #ifndef WIN32 - return !rmdir(path.c_str()); - #else - wstring wpath = OVUTF16::FromUTF8(path); - return RemoveDirectoryW(wpath.c_str()) == TRUE; - #endif - } - else { - #ifndef WIN32 - return !unlink(path.c_str()); - #else - wstring wpath = OVUTF16::FromUTF8(path); - return DeleteFileW(wpath.c_str()) == TRUE; - #endif - } - } -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVFrameworkInfo.h b/Source/3rdParty/OpenVanilla/OVFrameworkInfo.h deleted file mode 100644 index b07bf7032..000000000 --- a/Source/3rdParty/OpenVanilla/OVFrameworkInfo.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * OVFrameworkInfo.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVFrameworkVersion_h -#define OVFrameworkVersion_h - -#if defined(__APPLE__) - #include -#else - #include "OVBase.h" -#endif - -#include - -namespace OpenVanilla { - using namespace std; - - class OVFrameworkInfo { - public: - static unsigned int MajorVersion() - { - return c_MajorVersion; - } - - static unsigned int MinorVersion() - { - return c_MinorVersion; - } - - static unsigned int TinyVersion() - { - return c_TinyVersion; - } - - static unsigned int Version() - { - return ((c_MajorVersion & 0xff) << 24) | ((c_MinorVersion & 0xff)<< 16) | (c_TinyVersion & 0xffff); - } - - static unsigned int BuildNumber() - { - return c_FrameworkBuildNumber; - } - - static const string VersionString(bool withBuildNumber = false) - { - stringstream s; - s << c_MajorVersion << "." << c_MinorVersion << "." << c_TinyVersion; - if (withBuildNumber) - s << "." << c_FrameworkBuildNumber; - - return s.str(); - } - - static const string VersionStringWithBuildNumber() - { - return VersionString(true); - } - - protected: - static const unsigned int c_MajorVersion; - static const unsigned int c_MinorVersion; - static const unsigned int c_TinyVersion; - static const unsigned int c_FrameworkBuildNumber; - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVInputMethod.h b/Source/3rdParty/OpenVanilla/OVInputMethod.h deleted file mode 100644 index 600eaf416..000000000 --- a/Source/3rdParty/OpenVanilla/OVInputMethod.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * OVInputMethod.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVInputMethod_h -#define OVInputMethod_h - -#if defined(__APPLE__) - #include -#else - #include "OVModule.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVInputMethod : public OVModule { - public: - virtual bool isInputMethod() const - { - return true; - } - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVKey.h b/Source/3rdParty/OpenVanilla/OVKey.h deleted file mode 100644 index 3f3fcfb34..000000000 --- a/Source/3rdParty/OpenVanilla/OVKey.h +++ /dev/null @@ -1,283 +0,0 @@ -/* - * OVKey.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVKey_h -#define OVKey_h - -namespace OpenVanilla { - using namespace std; - - // keyCode() is 0 if receivedString() is a non-ASCII glyph/string - class OVKeyInterface : public OVBase { - public: - virtual const string receivedString() const = 0; - virtual unsigned int keyCode() const = 0; - virtual bool isAltPressed() const = 0; - virtual bool isOptPressed() const = 0; - virtual bool isCtrlPressed() const = 0; - virtual bool isShiftPressed() const = 0; - virtual bool isCommandPressed() const = 0; - virtual bool isNumLockOn() const = 0; - virtual bool isCapsLockOn() const = 0; - - // a direct text key carries a composed glyph (or a string) that semantically differs from the intended keystroke - // (i.e. a half-width char stroke but with a composed, full-width char output) - virtual bool isDirectTextKey() const = 0; - }; - - class OVKeyImpl : public OVKeyInterface { - public: - virtual bool shouldDelete() const = 0; - virtual OVKeyImpl* copy() = 0; - }; - - class OVKey : public OVKeyInterface { - public: - OVKey(OVKeyImpl* keyImpl = 0) - : m_keyImpl(keyImpl) - { - } - - OVKey(const OVKey& aKey) - { - m_keyImpl = aKey.m_keyImpl ? aKey.m_keyImpl->copy() : 0; - } - - ~OVKey() - { - if (m_keyImpl) { - if (m_keyImpl->shouldDelete()) { - delete m_keyImpl; - } - } - } - - OVKey& operator=(const OVKey& aKey) - { - if (m_keyImpl) { - if (m_keyImpl->shouldDelete()) { - delete m_keyImpl; - } - - m_keyImpl = 0; - } - - m_keyImpl = aKey.m_keyImpl ? aKey.m_keyImpl->copy() : 0; - return *this; - } - - virtual bool operator==(const OVKey& key) const - { - if (isAltPressed() == key.isAltPressed() && isOptPressed() == key.isOptPressed() && isCtrlPressed() == key.isCtrlPressed() && isShiftPressed() == key.isShiftPressed() && isCommandPressed() == key.isCommandPressed()) - if (!keyCode() && !key.keyCode()) - return receivedString() == key.receivedString(); - else - return keyCode() == key.keyCode(); - - return false; - } - - virtual bool operator<(const OVKey& key) const - { - if (keyCode() < key.keyCode()) return true; - if (keyCode() > key.keyCode()) return false; - - if (!keyCode() && !key.keyCode()) { - if (receivedString() < key.receivedString()) return true; - if (receivedString() > key.receivedString()) return false; - } - - if (isAltPressed() != key.isAltPressed()) { - if (key.isAltPressed()) return true; - if (isAltPressed()) return false; - } - - if (isOptPressed() != key.isOptPressed()) { - if (key.isOptPressed()) return true; - if (isOptPressed()) return false; - } - - if (isCtrlPressed() != key.isCtrlPressed()) { - if (key.isCtrlPressed()) return true; - if (isCtrlPressed()) return false; - } - - if (isShiftPressed() != key.isShiftPressed()) { - if (key.isShiftPressed()) return true; - if (isShiftPressed()) return false; - } - - if (isCommandPressed() != key.isCommandPressed()) { - if (key.isCommandPressed()) return true; - if (isCommandPressed()) return false; - } - - if (isNumLockOn() != key.isNumLockOn()) { - if (key.isNumLockOn()) return true; - if (isNumLockOn()) return false; - } - - if (isCapsLockOn() != key.isCapsLockOn()) { - if (key.isCapsLockOn()) return true; - if (isCapsLockOn()) return false; - } - - return false; - } - - public: - virtual const string receivedString() const - { - return m_keyImpl ? m_keyImpl->receivedString() : string(); - } - - virtual unsigned int keyCode() const - { - return m_keyImpl ? m_keyImpl->keyCode() : 0; - } - - virtual bool isAltPressed() const - { - return m_keyImpl ? m_keyImpl->isAltPressed() : false; - } - - virtual bool isOptPressed() const - { - return m_keyImpl ? m_keyImpl->isOptPressed() : false; - } - - virtual bool isCtrlPressed() const - { - return m_keyImpl ? m_keyImpl->isCtrlPressed() : false; - } - - virtual bool isShiftPressed() const - { - return m_keyImpl ? m_keyImpl->isShiftPressed() : false; - } - - virtual bool isCommandPressed() const - { - return m_keyImpl ? m_keyImpl->isCommandPressed() : false; - } - - virtual bool isNumLockOn() const - { - return m_keyImpl ? m_keyImpl->isNumLockOn() : false; - } - - virtual bool isCapsLockOn() const - { - return m_keyImpl ? m_keyImpl->isCapsLockOn() : false; - } - - virtual bool isDirectTextKey() const - { - return m_keyImpl ? m_keyImpl->isDirectTextKey() : false; - } - - virtual bool isKeyCodePrintable() const - { - if (keyCode() >= 32 && keyCode() <= 126) - return true; - - return false; - } - - virtual bool isKeyCodeNumeric() const - { - if (keyCode() >= '0' && keyCode() <= '9') - return true; - - return false; - } - - virtual bool isKeyCodeAlpha() const - { - if (keyCode() >= 'A' && keyCode() <= 'Z' || keyCode() >= 'a' && keyCode() <= 'z') - return true; - - return false; - } - - virtual bool isCombinedFunctionKey() const - { - return isCtrlPressed() || isAltPressed() || isOptPressed() || isCommandPressed(); - } - - virtual bool isPrintable() const - { - size_t rssize = receivedString().size(); - unsigned int code = keyCode(); - - if (!rssize) - return false; - - if (rssize > 1) - return true; - - return ((code < 128 && isprint((char)code)) || code > 128); - } - - protected: - OVKeyImpl* m_keyImpl; - }; - - typedef vector OVKeyVector; - - class OVKeyCode { - public: - enum { - Delete = 127, - Backspace = 8, - Up = 30, - Down = 31, - Left = 28, - Right = 29, - Home = 1, - End = 4, - PageUp = 11, - PageDown = 12, - Tab = 9, - Esc = 27, - Space = 32, - Return = 13, - Enter = Return, - LeftShift = 0x10001, - RightShift = 0x10002, - CapsLock = 0x10010, - MacEnter = 0x10020, - F1 = 0x11001, - F2 = 0x11002, - F3 = 0x11003, - F4 = 0x11004, - F5 = 0x11005, - F6 = 0x11006, - F7 = 0x11007, - F8 = 0x11008, - F9 = 0x11009, - F10 = 0x11010, - }; - }; - - class OVKeyMask { - public: - enum { - Alt = 0x0001, - Opt = 0x0002, - AltOpt = 0x0003, - Ctrl = 0x0004, - Shift = 0x008, - Command = 0x0010, - NumLock = 0x0020, - CapsLock = 0x0040, - DirectText = 0x0080 - }; - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVKeyPreprocessor.h b/Source/3rdParty/OpenVanilla/OVKeyPreprocessor.h deleted file mode 100644 index d48990c1f..000000000 --- a/Source/3rdParty/OpenVanilla/OVKeyPreprocessor.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * OVKeyPreprocessor.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVKeyPreprocessor_h -#define OVKeyPreprocessor_h - -#if defined(__APPLE__) - #include -#else - #include "OVModule.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVKeyPreprocessor : public OVModule { - public: - virtual bool isPreprocessor() const - { - return true; - } - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVKeyValueMap.h b/Source/3rdParty/OpenVanilla/OVKeyValueMap.h deleted file mode 100644 index 241379b80..000000000 --- a/Source/3rdParty/OpenVanilla/OVKeyValueMap.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * OVKeyValueMap.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVKeyValueMap_h -#define OVKeyValueMap_h - -#if defined(__APPLE__) - #include -#else - #include "OVBase.h" -#endif - -#include - -namespace OpenVanilla { - using namespace std; - - class OVKeyValueMapInterface : public OVBase { - public: - virtual bool isReadOnly() = 0; - virtual bool setKeyStringValue(const string& key, const string& value) = 0; - virtual bool hasKey(const string& key) = 0; - virtual const string stringValueForKey(const string& key) = 0; - - virtual bool setKeyIntValue(const string& key, int value) - { - stringstream sstr; - sstr << value; - return setKeyStringValue(key, sstr.str()); - } - - virtual bool setKeyBoolValue(const string& key, bool value) - { - if (value) - return setKeyStringValue(key, "true"); - - return setKeyStringValue(key, "false"); - } - - virtual int intValueForKey(const string& key) - { - string value = stringValueForKey(key); - return atoi(value.c_str()); - } - - virtual const string stringValueForKeyWithDefault(const string& key, const string& defaultValue = "", bool setIfNotFound = true) - { - if (hasKey(key)) - return stringValueForKey(key); - - if (setIfNotFound) - setKeyStringValue(key, defaultValue); - - return defaultValue; - } - - virtual const string operator[](const string& key) - { - return stringValueForKey(key); - } - - virtual bool isKeyTrue(const string& key) - { - if (!hasKey(key)) - return false; - - string value = stringValueForKey(key); - - if (atoi(value.c_str()) > 0) - return true; - - if (value == "true") - return true; - - return false; - } - }; - - class OVKeyValueMapImpl : public OVKeyValueMapInterface { - public: - virtual bool shouldDelete() = 0; - virtual OVKeyValueMapImpl* copy() = 0; - }; - - class OVKeyValueMap : public OVKeyValueMapInterface { - public: - OVKeyValueMap(OVKeyValueMapImpl* keyValueMapImpl = 0) - : m_keyValueMapImpl(keyValueMapImpl) - { - } - - OVKeyValueMap(const OVKeyValueMap& aKeyValueMap) - { - m_keyValueMapImpl = aKeyValueMap.m_keyValueMapImpl ? aKeyValueMap.m_keyValueMapImpl->copy() : 0; - } - - ~OVKeyValueMap() - { - if (m_keyValueMapImpl) { - if (m_keyValueMapImpl->shouldDelete()) { - delete m_keyValueMapImpl; - } - } - } - - OVKeyValueMap& operator=(const OVKeyValueMap& aKeyValueMap) - { - if (m_keyValueMapImpl) { - if (m_keyValueMapImpl->shouldDelete()) { - delete m_keyValueMapImpl; - } - - m_keyValueMapImpl = 0; - } - - m_keyValueMapImpl = aKeyValueMap.m_keyValueMapImpl ? aKeyValueMap.m_keyValueMapImpl->copy() : 0; - return *this; - } - - public: - virtual bool isReadOnly() - { - return m_keyValueMapImpl ? m_keyValueMapImpl->isReadOnly() : true; - } - - virtual bool setKeyStringValue(const string& key, const string& value) - { - return m_keyValueMapImpl ? m_keyValueMapImpl->setKeyStringValue(key, value) : false; - } - - virtual bool hasKey(const string& key) - { - return m_keyValueMapImpl ? m_keyValueMapImpl->hasKey(key) : false; - } - - virtual const string stringValueForKey(const string& key) - { - return m_keyValueMapImpl ? m_keyValueMapImpl->stringValueForKey(key) : string(); - } - - virtual const string stringValueForKeyWithDefault(const string& key, const string& defaultValue = "", bool setIfNotFound = true) - { - return m_keyValueMapImpl ? m_keyValueMapImpl->stringValueForKeyWithDefault(key, defaultValue, setIfNotFound) : string(); - } - - protected: - OVKeyValueMapImpl* m_keyValueMapImpl; - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVLoaderBase.h b/Source/3rdParty/OpenVanilla/OVLoaderBase.h deleted file mode 100644 index cf2b0614e..000000000 --- a/Source/3rdParty/OpenVanilla/OVLoaderBase.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * OVLoaderBase.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVLoaderBase_h -#define OVLoaderBase_h - -#if defined(__APPLE__) - #include -#else - #include "OVModule.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVLoader : public OVBase { - public: - virtual OVLoaderService* loaderService() = 0; - virtual OVModule* moduleForIdentifier(const string& identifier) = 0; - virtual vector moduleIdentifiers() = 0; - virtual vector moduleIdentifiersForConditions(bool preprocessor, bool inputMethod, bool outputFilter) = 0; - }; - -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVLoaderService.h b/Source/3rdParty/OpenVanilla/OVLoaderService.h deleted file mode 100644 index e0e63b6b5..000000000 --- a/Source/3rdParty/OpenVanilla/OVLoaderService.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * OVLoaderService.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVLoaderService_h -#define OVLoaderService_h - -#if defined(__APPLE__) - #include - #include - #include - #include -#else - #include "OVBase.h" - #include "OVDatabaseService.h" - #include "OVEncodingService.h" - #include "OVKey.h" -#endif - -#include -#include - -namespace OpenVanilla { - using namespace std; - - class OVLogEmitter : public OVBase { - public: - virtual const string sectionName() const = 0; - virtual void setSectionName(const string& sectionName) = 0; - virtual void emitLog(const string& logEntry) = 0; - }; - - class OVLogStringBuffer : public stringbuf { - public: - OVLogStringBuffer(OVLogEmitter* logEmitter = 0) - : m_logEmitter(logEmitter) - { - } - - virtual int sync() { - if (str().length()) { - if (m_logEmitter) - m_logEmitter->emitLog(str()); - else - cerr << "Log: " << str(); - - str(string()); - } - - // clear the buffer - return 0; - } - - virtual OVLogEmitter* logEmitter() const - { - return m_logEmitter; - } - - virtual void setLogEmitter(OVLogEmitter* logEmitter) - { - m_logEmitter = logEmitter; - } - - protected: - OVLogEmitter* m_logEmitter; - }; - - class OVLoaderService : public OVBase { - public: - virtual void beep() = 0; - virtual void notify(const string& message) = 0; - virtual void HTMLNotify(const string& content) = 0; - - virtual const string locale() const = 0; - virtual const OVKey makeOVKey(int characterCode, bool alt = false, bool opt = false, bool ctrl = false, bool shift = false, bool command = false, bool capsLock = false, bool numLock = false) = 0; - virtual const OVKey makeOVKey(const string& receivedString, bool alt = false, bool opt = false, bool ctrl = false, bool shift = false, bool command = false, bool capsLock = false, bool numLock = false) = 0; - - virtual ostream& logger(const string& sectionName = "") = 0; - - virtual OVDatabaseService* defaultDatabaseService() = 0; - virtual OVDatabaseService* CINDatabaseService() = 0; - virtual OVDatabaseService* SQLiteDatabaseService() = 0; - - virtual OVEncodingService* encodingService() = 0; - - virtual void __reserved1(const string&) = 0; - virtual void __reserved2(const string&) = 0; - virtual void __reserved3(const string&) = 0; - virtual void __reserved4(const string&) = 0; - virtual const string __reserved5() const = 0; - virtual void __reserved6(const string&) = 0; - virtual void __reserved7(const string&, const string &) = 0; - virtual void* __reserved8(const string&) = 0; - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVLocalization.h b/Source/3rdParty/OpenVanilla/OVLocalization.h deleted file mode 100644 index 329052e5d..000000000 --- a/Source/3rdParty/OpenVanilla/OVLocalization.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * OVLocalization.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVLocalization_h -#define OVLocalization_h - -#include -#include -#include "OVStringHelper.h" -#include "OVWildcard.h" - -namespace OpenVanilla { - using namespace std; - - class OVLocale { - public: - static const string POSIXLocaleID(const string& locale) - { - string n = OVStringHelper::StringByReplacingOccurrencesOfStringWithString(locale, "-", "_"); - - if (OVWildcard::Match(n, "zh_Hant")) { - return "zh_TW"; - } - - if (OVWildcard::Match(n, "zh_Hans")) { - return "zh_CN"; - } - - if (OVWildcard::Match(n, "zh_HK")) { - return "zh_TW"; - } - - if (OVWildcard::Match(n, "zh_SG")) { - return "zh_CN"; - } - - if (OVWildcard::Match(n, "en_*")) { - return "en"; - } - - return locale; - } - }; - - - template class OVLocalization { - public: - static const void SetDefaultLocale(const string& locale) - { - SharedInstance()->m_defaultLocale = locale.length() ? OVLocale::POSIXLocaleID(locale) : string("en"); - } - - static const string S(const string& locale, const string& text) - { - return SharedInstance()->m_table(locale, text); - } - - static const string S(const string& text) - { - return SharedInstance()->m_table(SharedInstance()->m_defaultLocale, text); - } - - protected: - static OVLocalization* SharedInstance() - { - static OVLocalization* instance = 0; - if (!instance) { - instance = new OVLocalization; - } - - return instance; - } - - OVLocalization() - : m_defaultLocale("en") - { - } - - T m_table; - string m_defaultLocale; - }; - - class OVLocalizationStringTable { - public: - const string operator()(const string& locale, const string& text) const - { - // maybe we'll have fallback logic later here - map >::const_iterator i = m_table.find(locale); - if (i == m_table.end()) { - return text; - } - - map::const_iterator j = (*i).second.find(text); - if (j == (*i).second.end()) { - return text; - } - - return (*j).second; - } - - - protected: - void add(const string& locale, const string& original, const string& localized) - { - m_table[locale][original] = localized; - } - - map > m_table; - }; -}; - -#endif \ No newline at end of file diff --git a/Source/3rdParty/OpenVanilla/OVModule.h b/Source/3rdParty/OpenVanilla/OVModule.h deleted file mode 100644 index bd90536c9..000000000 --- a/Source/3rdParty/OpenVanilla/OVModule.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * OVModule.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVModule_h -#define OVModule_h - -#if defined(__APPLE__) - #include - #include - #include -#else - #include "OVEventHandlingContext.h" - #include "OVKeyValueMap.h" - #include "OVPathInfo.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVModule : public OVBase { - public: - OVModule() - : m_initialized(false) - , m_usable(false) - { - } - - virtual bool isInitialized() const - { - return m_initialized; - } - - virtual bool isUsable() const - { - return m_usable; - } - - virtual bool isPreprocessor() const - { - return false; - } - - virtual bool isInputMethod() const - { - return false; - } - - virtual bool isOutputFilter() const - { - return false; - } - - virtual bool isAroundFilter() const - { - return false; - } - - // the smaller it gets, the closer the the filter gets to the commit event - virtual int suggestedOrder() const - { - return 0; - } - - virtual OVEventHandlingContext* createContext() - { - return 0; - } - - virtual const string identifier() const = 0; - - virtual const string localizedName(const string& locale) - { - return identifier(); - - } - - virtual bool moduleInitialize(OVPathInfo* pathInfo, OVLoaderService* loaderService) - { - if (m_initialized) - return false; - - m_usable = initialize(pathInfo, loaderService); - m_initialized = true; - return m_usable; - } - - virtual bool initialize(OVPathInfo* pathInfo, OVLoaderService* loaderService) - { - return true; - } - - virtual void finalize() - { - } - - virtual void loadConfig(OVKeyValueMap* moduleConfig, OVLoaderService* loaderService) - { - } - - virtual void saveConfig(OVKeyValueMap* moduleConfig, OVLoaderService* loaderService) - { - } - - enum AroundFilterDisplayOption { - ShownAsPreprocessor, - ShownAsOutputFilter, - ShownAsBoth - }; - - // around filter modules need to tell loader how it wishes to be placed in the menu - virtual AroundFilterDisplayOption aroundFilterPreferredDisplayOption() - { - return ShownAsBoth; - } - - protected: - bool m_initialized; - bool m_usable; - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVModulePackage.h b/Source/3rdParty/OpenVanilla/OVModulePackage.h deleted file mode 100644 index e94657f70..000000000 --- a/Source/3rdParty/OpenVanilla/OVModulePackage.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * OVModulePackage.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVModulePackage_h -#define OVModulePackage_h - -#if defined(__APPLE__) - #include - #include -#else - #include "OVFrameworkInfo.h" - #include "OVModule.h" -#endif - -#ifdef WIN32 - #define OVEXPORT __declspec(dllexport) -#else - #define OVEXPORT -#endif - -namespace OpenVanilla { - using namespace std; - - class OVModuleClassWrapperBase : public OVBase { - public: - virtual OVModule* newModule() - { - // this member function can't be abstract, or vector wouldn't instantiate under VC++ 2005 - return 0; - } - }; - - template class OVModuleClassWrapper : public OVModuleClassWrapperBase { - public: - virtual OVModule* newModule() - { - return new T; - } - }; - - // we encourage people to do the real initialization in initialize - class OVModulePackage : OVBase { - public: - ~OVModulePackage() - { - vector::iterator iter = m_moduleVector.begin(); - for ( ; iter != m_moduleVector.end(); ++iter) - delete *iter; - } - virtual bool initialize(OVPathInfo* , OVLoaderService* loaderService) - { - // in your derived class, add class wrappers to m_moduleVector - return true; - } - - virtual void finalize() - { - } - - virtual size_t numberOfModules(OVLoaderService*) - { - return m_moduleVector.size(); - } - - virtual OVModule* moduleAtIndex(size_t index, OVLoaderService*) - { - if (index > m_moduleVector.size()) return 0; - return m_moduleVector[index]->newModule(); - } - - protected: - vector m_moduleVector; - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVOutputFilter.h b/Source/3rdParty/OpenVanilla/OVOutputFilter.h deleted file mode 100644 index f0c4583b4..000000000 --- a/Source/3rdParty/OpenVanilla/OVOutputFilter.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * OVOutputFilter.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVOutputFilter_h -#define OVOutputFilter_h - -#if defined(__APPLE__) - #include -#else - #include "OVModule.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVOutputFilter : public OVModule { - public: - virtual bool isOutputFilter() const - { - return true; - } - }; -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVPathInfo.h b/Source/3rdParty/OpenVanilla/OVPathInfo.h deleted file mode 100644 index cce07863d..000000000 --- a/Source/3rdParty/OpenVanilla/OVPathInfo.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * OVPathInfo.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVPathInfo_h -#define OVPathInfo_h - -#if defined(__APPLE__) - #include -#else - #include "OVFileHelper.h" -#endif - -namespace OpenVanilla { - using namespace std; - - struct OVPathInfo { - string loadedPath; - string resourcePath; - string writablePath; - - static const OVPathInfo DefaultPathInfo() { - string tmpdir = OVDirectoryHelper::TempDirectory(); - OVPathInfo pathInfo; - - pathInfo.loadedPath = tmpdir; - pathInfo.resourcePath = tmpdir; - pathInfo.writablePath = tmpdir; - return pathInfo; - } - }; - - inline ostream& operator<<(ostream& stream, const OVPathInfo& info) - { - stream << "OVPathInfo = (loaded path = " << info.loadedPath << ", resource path = " << info.resourcePath << ", writable path = " << info.writablePath << ")"; - return stream; - } -}; - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVSQLiteDatabaseService.h b/Source/3rdParty/OpenVanilla/OVSQLiteDatabaseService.h deleted file mode 100644 index 7a1beb0a7..000000000 --- a/Source/3rdParty/OpenVanilla/OVSQLiteDatabaseService.h +++ /dev/null @@ -1,229 +0,0 @@ -/* - * OVSQLiteDatabaseService.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVSQLiteDatabaseService_h -#define OVSQLiteDatabaseService_h - -#if defined(__APPLE__) - #include - #include - #include -#else - #include "OVDatabaseService.h" - #include "OVSQLiteWrapper.h" - #include "OVWildcard.h" -#endif - -namespace OpenVanilla { - using namespace std; - - class OVSQLiteHelper { - public: - static const pair SQLiteStringFromWildcard(const OVWildcard& wildcard) - { - const string& expression = wildcard.expression(); - string sqlstr; - - char mOC = wildcard.matchOneChar(); - char mZOMC = wildcard.matchZeroOrMoreChar(); - char escChar = mZOMC ? mZOMC : mOC; - - for (string::const_iterator iter = expression.begin() ; iter != expression.end() ; ++iter) { - if (*iter == mOC) { - sqlstr += '_'; - } - else if (*iter == mZOMC) { - sqlstr += '%'; - } - else if (*iter == '_') { - sqlstr += escChar; - sqlstr += '_'; - } - else if (*iter == '%') { - sqlstr += escChar; - sqlstr += '%'; - } - else { - sqlstr += *iter; - } - } - - return pair(sqlstr, string(1, escChar)); - } - }; - - class OVSQLiteDatabaseService; - - class OVSQLiteKeyValueDataTable : public OVKeyValueDataTableInterface { - public: - virtual const vector valuesForKey(const string& key); - virtual const vector > valuesForKey(const OVWildcard& expression); - virtual const string valueForProperty(const string& property); - virtual const vector keysForValue(const string& value); - - protected: - OVSQLiteDatabaseService* m_source; - string m_tableName; - - friend class OVSQLiteDatabaseService; - OVSQLiteKeyValueDataTable(OVSQLiteDatabaseService* source, const string& tableName) - : m_source(source) - , m_tableName(tableName) - { - } - }; - - class OVSQLiteDatabaseService : public OVDatabaseService { - public: - ~OVSQLiteDatabaseService() - { - if (m_ownsConnection) - delete m_connection; - } - - virtual const vector tables(const OVWildcard& filter = string("*")) - { - pair exp = OVSQLiteHelper::SQLiteStringFromWildcard(filter); - - vector result; - OVSQLiteStatement* statement = m_connection->prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE %Q ESCAPE %Q ORDER BY name", exp.first.c_str(), exp.second.c_str()); - if (statement) { - while (statement->step() == SQLITE_ROW) { - result.push_back(statement->textOfColumn(0)); - } - - delete statement; - } - - return result; - } - - virtual bool tableSupportsValueToKeyLookup(const string &tableName) - { - return true; - } - - virtual OVKeyValueDataTableInterface* createKeyValueDataTableInterface(const string& name, bool suggestedCaseSensitivity = false) - { - return new OVSQLiteKeyValueDataTable(this, name); - } - - virtual const string valueForPropertyInTable(const string& property, const string& name) - { - OVSQLiteStatement* statement = m_connection->prepare("SELECT VALUE FROM %Q WHERE KEY = ?", name.c_str()); - string result; - - if (statement) { - statement->bindTextToColumn(string(OVPropertyStringInternalPrefix) + property, 1); - - if (statement->step() == SQLITE_ROW) { - result = statement->textOfColumn(0); - while (statement->step() == SQLITE_ROW) ; - } - - delete statement; - } - - return result; - } - - virtual const string filename() - { - return m_connection->filename(); - } - - static OVSQLiteDatabaseService* Create(const string& filename = ":memory:") - { - OVSQLiteConnection* connection = OVSQLiteConnection::Open(filename); - if (!connection) - return 0; - - return new OVSQLiteDatabaseService(connection, true); - } - - static OVSQLiteDatabaseService* ServiceWithExistingConnection(OVSQLiteConnection* connection, bool ownsConnection = false) - { - return new OVSQLiteDatabaseService(connection, ownsConnection); - } - - OVSQLiteConnection* connection() - { - return m_connection; - } - - protected: - friend class OVSQLiteKeyValueDataTable; - - OVSQLiteDatabaseService(OVSQLiteConnection* connection, bool ownsConnection = false) - : m_connection(connection) - , m_ownsConnection(ownsConnection) - { - } - - OVSQLiteConnection* m_connection; - bool m_ownsConnection; - }; - - inline const vector OVSQLiteKeyValueDataTable::valuesForKey(const string& key) - { - vector result; - OVSQLiteStatement* statement = m_source->connection()->prepare("SELECT value FROM %Q WHERE key = %Q", m_tableName.c_str(), key.c_str()); - if (statement) { - while (statement->step() == SQLITE_ROW) { - result.push_back(statement->textOfColumn(0)); - } - - delete statement; - } - - return result; - } - - inline const vector OVSQLiteKeyValueDataTable::keysForValue(const string& value) - { - vector result; - OVSQLiteStatement* statement = m_source->connection()->prepare("SELECT key FROM %Q WHERE value = %Q", m_tableName.c_str(), value.c_str()); - if (statement) { - while (statement->step() == SQLITE_ROW) { - string key = statement->textOfColumn(0); - - // we don't want property get into it - if (!OVWildcard::Match(key, OVPropertyStringInternalPrefix "*")) { - result.push_back(key); - } - } - - delete statement; - } - - return result; - } - - inline const vector > OVSQLiteKeyValueDataTable::valuesForKey(const OVWildcard& expression) - { - pair exp = OVSQLiteHelper::SQLiteStringFromWildcard(expression); - - vector > result; - OVSQLiteStatement* statement = m_source->connection()->prepare("SELECT key, value FROM %Q WHERE key like %Q escape %Q", m_tableName.c_str(), exp.first.c_str(), exp.second.c_str()); - if (statement) { - while (statement->step() == SQLITE_ROW) { - result.push_back(pair(statement->textOfColumn(0), statement->textOfColumn(1))); - } - - delete statement; - } - - return result; - } - - inline const string OVSQLiteKeyValueDataTable::valueForProperty(const string& property) - { - return m_source->valueForPropertyInTable(property, m_tableName); - } -}; - -#endif \ No newline at end of file diff --git a/Source/3rdParty/OpenVanilla/OVSQLiteWrapper.h b/Source/3rdParty/OpenVanilla/OVSQLiteWrapper.h deleted file mode 100644 index c00dba572..000000000 --- a/Source/3rdParty/OpenVanilla/OVSQLiteWrapper.h +++ /dev/null @@ -1,258 +0,0 @@ -/* - * OVSQLiteWrapper.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVSQLite3Wrapper -#define OVSQLite3Wrapper - -#include - -#if defined(__APPLE__) - #include -#else - #include "OpenVanilla.h" -#endif - -// For status codes (SQLITE_OK, SQLITE_ERROR, etc.), please refer to sqlite3.h - -namespace OpenVanilla { - using namespace std; - - class OVSQLiteStatement; - - class OVSQLiteConnection { - public: - // remember to manage the object returned by this class member function - static OVSQLiteConnection* Open(const string& filename = ":memory:"); - ~OVSQLiteConnection(); - - int lastError(); - const char* lastErrorMessage(); - - int execute(const char* sqlcmd, ...); - OVSQLiteStatement* prepare(const char* sqlcmd, ...); - - // helper for table creation and detection - bool hasTable(const string& tableName); - bool dropTable(const string& tableName); - bool createTable(const string& tableName, const string& columnString = "key, value", bool dropIfExists = false); - bool createIndexOnTable(const string& indexName, const string& tableName, const string& indexColumns = "key"); - - sqlite3* connection(); - const string filename(); - - protected: - sqlite3* m_connection; - string m_filename; - OVSQLiteConnection(sqlite3* connection, const string& filename); - }; - - class OVSQLiteStatement { - public: - ~OVSQLiteStatement(); - - int reset(); - - // *nota bene* column starts from 1 ! - int bindTextToColumn(const char*str, int column); - int bindTextToColumn(const string& str, int column); - int bindIntToColumn(int value, int column); - int bindDoubleToColumn(double value, int column); - - int step(); - int columnCount(); - const char* textOfColumn(int column); - int intOfColumn(int column); - double doubleOfColumn(int column); - - protected: - sqlite3_stmt* m_statement; - - friend class OVSQLiteConnection; - OVSQLiteStatement(sqlite3_stmt* statement); - }; - - inline sqlite3* OVSQLiteConnection::connection() - { - return m_connection; - } - - inline OVSQLiteConnection* OVSQLiteConnection::Open(const string& filename) - { - sqlite3* connection; - - if (sqlite3_open(filename.c_str(), &connection) != SQLITE_OK) - return 0; - - if (!connection) - return 0; - - return new OVSQLiteConnection(connection, filename); - } - - inline OVSQLiteConnection::OVSQLiteConnection(sqlite3* connection, const string& filename) - : m_connection(connection) - , m_filename(filename) - { - } - - inline OVSQLiteConnection::~OVSQLiteConnection() - { - - int result = sqlite3_close(m_connection); - if (result) { - ; - } - } - - inline const string OVSQLiteConnection::filename() - { - return m_filename; - } - - inline int OVSQLiteConnection::lastError() - { - return sqlite3_errcode(m_connection); - } - - inline const char* OVSQLiteConnection::lastErrorMessage() - { - return sqlite3_errmsg(m_connection); - } - - inline int OVSQLiteConnection::execute(const char* sqlcmd, ...) - { - va_list l; - va_start(l, sqlcmd); - char* cmd = sqlite3_vmprintf(sqlcmd, l); - va_end(l); - int result = sqlite3_exec(m_connection, cmd, NULL, NULL, NULL); - sqlite3_free(cmd); - return result; - } - - inline OVSQLiteStatement* OVSQLiteConnection::prepare(const char* sqlcmd, ...) - { - va_list l; - va_start(l, sqlcmd); - char* cmd = sqlite3_vmprintf(sqlcmd, l); - va_end(l); - - sqlite3_stmt* stmt; - OVSQLiteStatement* result = 0; - - const char* remainingSt = 0; - if (sqlite3_prepare_v2(m_connection, cmd, -1, &stmt, &remainingSt) == SQLITE_OK) - result = new OVSQLiteStatement(stmt); - sqlite3_free(cmd); - - return result; - } - - inline bool OVSQLiteConnection::hasTable(const string& tableName) - { - bool result = false; - OVSQLiteStatement* statement = prepare("SELECT name FROM sqlite_master WHERE name = %Q", tableName.c_str()); - if (statement) { - if (statement->step() == SQLITE_ROW) { - result = true; - while (statement->step() == SQLITE_ROW) ; - } - - delete statement; - } - - return result; - } - - inline bool OVSQLiteConnection::dropTable(const string& tableName) - { - return SQLITE_OK == execute("DROP TABLE %Q", tableName.c_str()); - } - - inline bool OVSQLiteConnection::createTable(const string& tableName, const string& columnString, bool dropIfExists) - { - if (hasTable(tableName)) { - if (dropIfExists) { - if (!dropTable(tableName)) - return false; - } - else { - return false; - } - } - - - return SQLITE_OK == execute("CREATE TABLE %Q (%s)", tableName.c_str(), columnString.c_str()); - } - - inline bool OVSQLiteConnection::createIndexOnTable(const string& indexName, const string& tableName, const string& indexColumns) - { - return SQLITE_OK == execute("CREATE INDEX %Q on %Q (%s)", indexName.c_str(), tableName.c_str(), indexColumns.c_str()); - } - - inline OVSQLiteStatement::OVSQLiteStatement(sqlite3_stmt* statement) - : m_statement(statement) - { - } - - inline OVSQLiteStatement::~OVSQLiteStatement() - { - sqlite3_finalize(m_statement); - } - - inline int OVSQLiteStatement::reset() - { - return sqlite3_reset(m_statement); - } - - inline int OVSQLiteStatement::bindTextToColumn(const char* str, int column) - { - return sqlite3_bind_text(m_statement, column, str, -1, SQLITE_TRANSIENT); - } - - inline int OVSQLiteStatement::bindTextToColumn(const string& str, int column) - { - return bindTextToColumn(str.c_str(), column); - } - - inline int OVSQLiteStatement::bindIntToColumn(int value, int column) - { - return sqlite3_bind_int(m_statement, column, value); - } - - inline int OVSQLiteStatement::bindDoubleToColumn(double value, int column) - { - return sqlite3_bind_double(m_statement, column, value); - } - - inline int OVSQLiteStatement::step() - { - return sqlite3_step(m_statement); - } - - inline int OVSQLiteStatement::columnCount() - { - return sqlite3_column_count(m_statement); - } - - inline const char* OVSQLiteStatement::textOfColumn(int column) - { - return (const char*)sqlite3_column_text(m_statement, column); - } - - inline int OVSQLiteStatement::intOfColumn(int column) - { - return sqlite3_column_int(m_statement, column); - } - - inline double OVSQLiteStatement::doubleOfColumn(int column) - { - return sqlite3_column_double(m_statement, column); - } -}; - -#endif \ No newline at end of file diff --git a/Source/3rdParty/OpenVanilla/OVStringHelper.h b/Source/3rdParty/OpenVanilla/OVStringHelper.h deleted file mode 100644 index 3bf740466..000000000 --- a/Source/3rdParty/OpenVanilla/OVStringHelper.h +++ /dev/null @@ -1,198 +0,0 @@ -/* - * OVStringHelper.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVStringHelper_h -#define OVStringHelper_h - -#include -#include -#include - -namespace OpenVanilla { - using namespace std; - - class OVStringHelper { - public: - static const vector SplitBySpacesOrTabsWithDoubleQuoteSupport(const string& text) - { - vector result; - size_t index = 0, last = 0, length = text.length(); - while (index < length) { - if (text[index] == '\"') { - index++; - string tmp; - while (index < length) { - if (text[index] == '\"') { - index++; - break; - } - - if (text[index] == '\\' && index + 1 < length) { - index++; - char c = text[index]; - switch (c) { - case 'r': - tmp += '\r'; - break; - case 'n': - tmp += '\n'; - break; - case '\"': - tmp += '\"'; - break; - case '\\': - tmp += '\\'; - break; - } - } - else { - tmp += text[index]; - } - - index++; - } - result.push_back(tmp); - } - - if (text[index] != ' ' && text[index] != '\t') { - last = index; - while (index < length) { - if (text[index] == ' ' || text[index] == '\t') { - if (index - last) - result.push_back(text.substr(last, index - last)); - break; - } - index++; - } - - if (index == length && index - last) - result.push_back(text.substr(last, index - last)); - } - - index++; - } - - return result; - } - - static const vector SplitBySpacesOrTabs(const string& text) - { - vector result; - size_t index = 0, last = 0, length = text.length(); - while (index < length) { - if (text[index] != ' ' && text[index] != '\t') { - last = index; - while (index < length) { - if (text[index] == ' ' || text[index] == '\t') { - if (index - last) - result.push_back(text.substr(last, index - last)); - break; - } - index++; - } - - if (index == length && index - last) - result.push_back(text.substr(last, index - last)); - } - - index++; - } - - return result; - } - - static const vector Split(const string& text, char c) - { - vector result; - size_t index = 0, last = 0, length = text.length(); - while (index < length) { - while (index < length) { - if (text[index] == c) { - result.push_back(text.substr(last, index - last)); - last = index + 1; - break; - } - index++; - } - - index++; - } - - if (last <= index) { - result.push_back(text.substr(last, index - last)); - } - - return result; - } - - // named after Cocoa's NSString -stringByReplacingOccurrencesOfString:WithString:, horrible - static const string StringByReplacingOccurrencesOfStringWithString(const string& source, const string& substr, const string& replacement) - { - if (!substr.length()) - return source; - - size_t pos; - if ((pos = source.find(substr)) >= source.length()) - return source; - - return source.substr(0, pos) + replacement + StringByReplacingOccurrencesOfStringWithString(source.substr(pos + substr.length()), substr, replacement); - } - - static const string Join(const vector& vec) - { - string result; - for (vector::const_iterator iter = vec.begin(); iter != vec.end() ; ++iter) - result += *iter; - - return result; - } - - static const string Join(const vector& vec, const string& separator) - { - return Join(vec.begin(), vec.end(), separator); - } - - static const string Join(const vector& vec, size_t from, size_t size, const string& separator) - { - return Join(vec.begin() + from, vec.begin() + from + size, separator); - } - - static const string Join(vector::const_iterator begin, vector::const_iterator end, const string& separator) - { - string result; - for (vector::const_iterator iter = begin ; iter != end ; ) { - result += *iter; - if (++iter != end) - result += separator; - } - return result; - } - - static const string PercentEncode(const string& text) - { - stringstream sst; - sst << hex; - - for (string::const_iterator i = text.begin() ; i != text.end() ; ++i) { - if ((*i >= '0' && *i <= '9') || (*i >= 'A' && *i <= 'Z') || (*i >= 'a' && *i <= 'z') || *i == '-' || *i == '_' || *i == '.' || *i == '~') { - sst << (char)*i; - } - else { - sst << '%'; - sst.width(2); - sst.fill('0'); - unsigned char c = *i; - sst << (unsigned int)c; - } - } - - return sst.str(); - } - }; -} - -#endif diff --git a/Source/3rdParty/OpenVanilla/OVTextBuffer.h b/Source/3rdParty/OpenVanilla/OVTextBuffer.h deleted file mode 100644 index 46fc4a5b4..000000000 --- a/Source/3rdParty/OpenVanilla/OVTextBuffer.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * OVTextBuffer.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OVTextBuffer_h -#define OVTextBuffer_h - -#if defined(__APPLE__) - #include -#else - #include "OVBase.h" -#endif - -namespace OpenVanilla { - using namespace std; - - // Terminologies: - // * Text buffer = a generic term - // * Composing text = composing buffer = composition buffer - // In Windows speak, "composition buffer" doesn't contain reading - // (Reading is a separate concept in Windows) - // In Mac speak, "composing buffer" == composing text + reading text - // * Reading text = currently in-wait-state radicals - // * Pre-edit, pre-edit area or pre-edit buffer (X11 speak) = composing text + reading - // Mac's composing buffer is actually pre-edit in this sense - - - class OVTextBuffer : public OVBase { - public: - - virtual void clear() = 0; - virtual void setText(const string& text) = 0; - virtual void appendText(const string& text, bool moveCursor = true) = 0; - - // when the text buffer is committed, the buffer itself, along with settings like cursor, and tooltip (for both composing text and reading text), highlight, word segments, and suggested display style (for reading text) are cleared; the combined committed string will be available at composedCommittedText() - virtual void commit() = 0; - virtual void commitAsTextSegment() = 0; - virtual void commit(const string& text) = 0; - virtual void commitAsTextSegment(const string& text) = 0; - - virtual void updateDisplay() = 0; - virtual bool isEmpty() const = 0; - virtual size_t codePointCount() const = 0; - virtual const string codePointAt(size_t index) const = 0; - virtual const string composedText() const = 0; - virtual const string composedCommittedText() const = 0; - virtual const vector composedCommittedTextSegments() const = 0; - - // Composing text (composing buffer, composition buffer)-only members - public: - typedef pair RangePair; - - // composing buffer should support these four, but reading buffer doesn't need to (anyway it's meaningless) - virtual void setCursorPosition(size_t position) = 0; - virtual size_t cursorPosition() const = 0; - virtual void showToolTip(const string& text) = 0; - virtual void clearToolTip() = 0; - - // implementation details: composing buffer might support this, but reading buffer doesn't - virtual void setHighlightMark(const RangePair& range) = 0; - - // word segments: on Windows, this is an independent (though application-dependent) feature, on Mac OS X, word segments cannot overlap with the highlight mark, and also, when the highlight mark is on, the cursor is off - virtual void setWordSegments(const vector& segments) = 0; - - // Reading text only members - public: - enum ReadingTextStyle { - Horizontal = 0, - Vertical = 1 - }; - - // this is only for the reading buffer, and is not a required implementation - virtual void setSuggestedReadingTextStyle(ReadingTextStyle style) = 0; - virtual ReadingTextStyle defaultReadingTextStyle() const = 0; - }; -}; - -#endif - diff --git a/Source/3rdParty/OpenVanilla/OVUTF8Helper.h b/Source/3rdParty/OpenVanilla/OVUTF8Helper.h index 53e70d1c9..d7a562e05 100644 --- a/Source/3rdParty/OpenVanilla/OVUTF8Helper.h +++ b/Source/3rdParty/OpenVanilla/OVUTF8Helper.h @@ -2,7 +2,7 @@ * OVUTF8Helper.h * * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. + * All rights reserved. See "LICENSE.TXT" for details. */ #ifndef OVUTF8Helper_h diff --git a/Source/3rdParty/OpenVanilla/OVWildcard.h b/Source/3rdParty/OpenVanilla/OVWildcard.h index b8cf2abb7..bc4fd6a3e 100644 --- a/Source/3rdParty/OpenVanilla/OVWildcard.h +++ b/Source/3rdParty/OpenVanilla/OVWildcard.h @@ -2,7 +2,7 @@ * OVWildcard.h * * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. + * All rights reserved. See "LICENSE.TXT" for details. */ #ifndef OVWildcard_h diff --git a/Source/3rdParty/OpenVanilla/OpenVanilla.h b/Source/3rdParty/OpenVanilla/OpenVanilla.h deleted file mode 100644 index 816fe3d5c..000000000 --- a/Source/3rdParty/OpenVanilla/OpenVanilla.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * OpenVanilla.h - * - * Copyright 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -#ifndef OpenVanilla_h -#define OpenVanilla_h - -#if defined(__APPLE__) - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - - #ifdef OV_USE_SQLITE - #include - #include - #endif -#else - #ifdef WIN32 - #include - #endif - - #include "OVAroundFilter.h" - #include "OVBase.h" - #include "OVBenchmark.h" - #include "OVCandidateService.h" - #include "OVCINDataTable.h" - #include "OVCINDatabaseService.h" - #include "OVDatabaseService.h" - #include "OVDateTimeHelper.h" - #include "OVEventHandlingContext.h" - #include "OVFileHelper.h" - #include "OVFrameworkInfo.h" - #include "OVInputMethod.h" - #include "OVLocalization.h" - #include "OVKey.h" - #include "OVKeyValueMap.h" - #include "OVLoaderService.h" - #include "OVModule.h" - #include "OVModulePackage.h" - #include "OVOutputFilter.h" - #include "OVPathInfo.h" - #include "OVStringHelper.h" - #include "OVTextBuffer.h" - #include "OVUTF8Helper.h" - #include "OVWildcard.h" - - #ifdef OV_USE_SQLITE - #include "OVSQLiteDatabaseService.h" - #include "OVSQLiteWrapper.h" - #endif -#endif - -#endif diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 62bae8a60..6529b2f0f 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -11,7 +11,6 @@ #import #import #import -#import "OVStringHelper.h" #import "OVUTF8Helper.h" #import "LanguageModelManager.h" #import "vChewing-Swift.h" diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 8c4aadd51..37204e57e 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -10,7 +10,6 @@ #import #import #import -#import "OVStringHelper.h" #import "OVUTF8Helper.h" using namespace std; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 69169ef1f..951d62698 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -197,36 +197,6 @@ 6A0D4F1E15FC0EB100ABF4B3 /* Walker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Walker.h; sourceTree = ""; }; 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Mandarin.cpp; sourceTree = ""; }; 6A0D4F2115FC0EB100ABF4B3 /* Mandarin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Mandarin.h; sourceTree = ""; }; - 6A0D4F2315FC0EB100ABF4B3 /* OpenVanilla.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenVanilla.h; sourceTree = ""; }; - 6A0D4F2415FC0EB100ABF4B3 /* OVAroundFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVAroundFilter.h; sourceTree = ""; }; - 6A0D4F2515FC0EB100ABF4B3 /* OVBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVBase.h; sourceTree = ""; }; - 6A0D4F2615FC0EB100ABF4B3 /* OVBenchmark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVBenchmark.h; sourceTree = ""; }; - 6A0D4F2715FC0EB100ABF4B3 /* OVCandidateService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVCandidateService.h; sourceTree = ""; }; - 6A0D4F2815FC0EB100ABF4B3 /* OVCINDatabaseService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVCINDatabaseService.h; sourceTree = ""; }; - 6A0D4F2915FC0EB100ABF4B3 /* OVCINDataTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVCINDataTable.h; sourceTree = ""; }; - 6A0D4F2A15FC0EB100ABF4B3 /* OVCINToSQLiteConvertor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVCINToSQLiteConvertor.h; sourceTree = ""; }; - 6A0D4F2B15FC0EB100ABF4B3 /* OVDatabaseService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVDatabaseService.h; sourceTree = ""; }; - 6A0D4F2C15FC0EB100ABF4B3 /* OVDateTimeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVDateTimeHelper.h; sourceTree = ""; }; - 6A0D4F2D15FC0EB100ABF4B3 /* OVEncodingService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVEncodingService.h; sourceTree = ""; }; - 6A0D4F2E15FC0EB100ABF4B3 /* OVEventHandlingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVEventHandlingContext.h; sourceTree = ""; }; - 6A0D4F2F15FC0EB100ABF4B3 /* OVException.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVException.h; sourceTree = ""; }; - 6A0D4F3015FC0EB100ABF4B3 /* OVFileHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVFileHelper.h; sourceTree = ""; }; - 6A0D4F3115FC0EB100ABF4B3 /* OVFrameworkInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVFrameworkInfo.h; sourceTree = ""; }; - 6A0D4F3215FC0EB100ABF4B3 /* OVInputMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVInputMethod.h; sourceTree = ""; }; - 6A0D4F3315FC0EB100ABF4B3 /* OVKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVKey.h; sourceTree = ""; }; - 6A0D4F3415FC0EB100ABF4B3 /* OVKeyPreprocessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVKeyPreprocessor.h; sourceTree = ""; }; - 6A0D4F3515FC0EB100ABF4B3 /* OVKeyValueMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVKeyValueMap.h; sourceTree = ""; }; - 6A0D4F3615FC0EB100ABF4B3 /* OVLoaderBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVLoaderBase.h; sourceTree = ""; }; - 6A0D4F3715FC0EB100ABF4B3 /* OVLoaderService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVLoaderService.h; sourceTree = ""; }; - 6A0D4F3815FC0EB100ABF4B3 /* OVLocalization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVLocalization.h; sourceTree = ""; }; - 6A0D4F3915FC0EB100ABF4B3 /* OVModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVModule.h; sourceTree = ""; }; - 6A0D4F3A15FC0EB100ABF4B3 /* OVModulePackage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVModulePackage.h; sourceTree = ""; }; - 6A0D4F3B15FC0EB100ABF4B3 /* OVOutputFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVOutputFilter.h; sourceTree = ""; }; - 6A0D4F3C15FC0EB100ABF4B3 /* OVPathInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVPathInfo.h; sourceTree = ""; }; - 6A0D4F3D15FC0EB100ABF4B3 /* OVSQLiteDatabaseService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVSQLiteDatabaseService.h; sourceTree = ""; }; - 6A0D4F3E15FC0EB100ABF4B3 /* OVSQLiteWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVSQLiteWrapper.h; sourceTree = ""; }; - 6A0D4F3F15FC0EB100ABF4B3 /* OVStringHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVStringHelper.h; sourceTree = ""; }; - 6A0D4F4015FC0EB100ABF4B3 /* OVTextBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVTextBuffer.h; sourceTree = ""; }; 6A0D4F4115FC0EB100ABF4B3 /* OVUTF8Helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVUTF8Helper.h; sourceTree = ""; }; 6A0D4F4215FC0EB100ABF4B3 /* OVWildcard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVWildcard.h; sourceTree = ""; }; 6A0D4F4915FC0EE100ABF4B3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -516,36 +486,6 @@ isa = PBXGroup; children = ( 6A0D4F1F15FC0EB100ABF4B3 /* Mandarin */, - 6A0D4F2315FC0EB100ABF4B3 /* OpenVanilla.h */, - 6A0D4F2415FC0EB100ABF4B3 /* OVAroundFilter.h */, - 6A0D4F2515FC0EB100ABF4B3 /* OVBase.h */, - 6A0D4F2615FC0EB100ABF4B3 /* OVBenchmark.h */, - 6A0D4F2715FC0EB100ABF4B3 /* OVCandidateService.h */, - 6A0D4F2815FC0EB100ABF4B3 /* OVCINDatabaseService.h */, - 6A0D4F2915FC0EB100ABF4B3 /* OVCINDataTable.h */, - 6A0D4F2A15FC0EB100ABF4B3 /* OVCINToSQLiteConvertor.h */, - 6A0D4F2B15FC0EB100ABF4B3 /* OVDatabaseService.h */, - 6A0D4F2C15FC0EB100ABF4B3 /* OVDateTimeHelper.h */, - 6A0D4F2D15FC0EB100ABF4B3 /* OVEncodingService.h */, - 6A0D4F2E15FC0EB100ABF4B3 /* OVEventHandlingContext.h */, - 6A0D4F2F15FC0EB100ABF4B3 /* OVException.h */, - 6A0D4F3015FC0EB100ABF4B3 /* OVFileHelper.h */, - 6A0D4F3115FC0EB100ABF4B3 /* OVFrameworkInfo.h */, - 6A0D4F3215FC0EB100ABF4B3 /* OVInputMethod.h */, - 6A0D4F3315FC0EB100ABF4B3 /* OVKey.h */, - 6A0D4F3415FC0EB100ABF4B3 /* OVKeyPreprocessor.h */, - 6A0D4F3515FC0EB100ABF4B3 /* OVKeyValueMap.h */, - 6A0D4F3615FC0EB100ABF4B3 /* OVLoaderBase.h */, - 6A0D4F3715FC0EB100ABF4B3 /* OVLoaderService.h */, - 6A0D4F3815FC0EB100ABF4B3 /* OVLocalization.h */, - 6A0D4F3915FC0EB100ABF4B3 /* OVModule.h */, - 6A0D4F3A15FC0EB100ABF4B3 /* OVModulePackage.h */, - 6A0D4F3B15FC0EB100ABF4B3 /* OVOutputFilter.h */, - 6A0D4F3C15FC0EB100ABF4B3 /* OVPathInfo.h */, - 6A0D4F3D15FC0EB100ABF4B3 /* OVSQLiteDatabaseService.h */, - 6A0D4F3E15FC0EB100ABF4B3 /* OVSQLiteWrapper.h */, - 6A0D4F3F15FC0EB100ABF4B3 /* OVStringHelper.h */, - 6A0D4F4015FC0EB100ABF4B3 /* OVTextBuffer.h */, 6A0D4F4115FC0EB100ABF4B3 /* OVUTF8Helper.h */, 6A0D4F4215FC0EB100ABF4B3 /* OVWildcard.h */, ); -- Gitee From 393fee5c2323571880681ca68de8ed3bfb03b654 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 26 Jan 2022 19:54:14 +0800 Subject: [PATCH 125/163] Use C++17 during build process. --- vChewing.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 951d62698..82d8da374 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -880,6 +880,8 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -922,6 +924,8 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; -- Gitee From e88c8387b64067bb32f67b67a482248119298b17 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 26 Jan 2022 19:56:38 +0800 Subject: [PATCH 126/163] =?UTF-8?q?README.MD=20//=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=A9=B3=E7=B4=B0=E7=9A=84=E7=B3=BB=E7=B5=B1=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E8=AA=AA=E6=98=8E=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0fb47def4..9265f9069 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,31 @@ 威注音分支专案及威注音词库由孙志贵(Shiki Suen)维护。小麦注音官方原始仓库內的词库的内容均与孙志贵无关。 -## 建置流程 +## 系统需求 建置用系统需求:至少 macOS 10.15 Catalina & Xcode 12。// 原因:Swift 封包管理支持所需。 -編譯出的成品對應系統需求:至少 macOS 10.12 Sierra。 + +编译出的成品对应系统需求: + +- 至少 macOS El Capitan 10.11.5,否则无法处理 Unicode 8.0 的汉字。即便如此,仍需手动升级苹方至至少 macOS 10.12 开始随赠的版本、以支持 Unicode 8.0 的通用规范汉字表用字(全字库没有「𫫇」字)。 + + - 保留该系统支援的原因:非 Unibody 体型的 MacBook Pro 支援的最后一版 macOS 就是 El Capitan。 + +- **推荐最低系统版本**:macOS 10.12 Sierra,对 Unicode 8.0 开始的《通用规范汉字表》汉字有原生的苹方支持。 + + - 注:能装 macOS 10.13 High Sierra 就不要去碰 macOS 10.12 Sierra 这个半成品。 + +- 关于全字库支持,因下述事实而在理论上很难做到最完美: + + - 很可惜 GB18030-2005 并没有官方提供的逐字读音对照表,所以目前才用了全字库。然而全字库并不等于完美。 + + - 有条件者可以安装全字库字型与花园明朝,否则全字库等高万国码码位汉字恐无法在输入法的选字窗内完整显示。 + + - 全字库汉字显示支持会受到具体系统版本对万国码版本的支持的限制。 + + - 有些全字库汉字一开始会依赖万国码的私人造字区,且在之后被新版本万国码所支持。 + +## 建置流程 安装 Xcode 之后,请先配置 Xcode 允许其直接构建在专案所在的资料夹下的 build 资料夹内。步骤: ``` @@ -21,12 +42,16 @@ 要注意的是 macOS 可能会限制同一次 login session 能终结同一个输入法的执行进程的次数(安装程式透过 kill input method process 来让新版的输入法生效)。如果安装若干次后,发现程式修改的结果并没有出现、或甚至输入法已无法再选用,只需要登出目前的 macOS 系统帐号、再重新登入即可。 -补记: 该输入法是在 2021 年 11 月初「28ae7deb4092f067539cff600397292e66a5dd56」这一版小麦注音建置的基础上完成的。因为在清洗词库的时候清洗了全部的 git commit 历史,所以无法自动从小麦注音官方仓库上游继承任何改动,只能手动同步任何在此之后的程式修正。最近一次同步參照是「ffbfe42cb1c17f2c626ca31bda604fb5c9ec56b3」。 +补记: 该输入法是在 2021 年 11 月初「28ae7deb4092f067539cff600397292e66a5dd56」这一版小麦注音建置的基础上完成的。因为在清洗词库的时候清洗了全部的 git commit 历史,所以无法自动从小麦注音官方仓库上游继承任何改动,只能手动同步任何在此之后的程式修正。最近一次同步參照是小麦注音 2.0.1 版。 ## 应用授权 -小麦注音引擎程式版权(MIT 授权):© 2011-2021 OpenVanilla 专案团队(Mengjuei Hsieh 等人)。 +小麦注音引擎程式版权(MIT 授权):© 2011-2021 OpenVanilla 专案团队。 + +威注音输入法 macOS 版以 3-Clause BSD License 授权释出 (与 MIT 相容):© 2021-2022 vChewing 专案。 + +威注音输入法 macOS 版程式维护:Shiki Suen。特别感谢 Hiraku Wong 等人的技术协力。 -威注音词库由孙志贵维护,以 MIT-MTL License 授权释出。 +威注音词库由孙志贵维护,以 3-Clause BSD License 授权释出。 -本专案采用 MIT-MTL License 释出:使用者可自由使用、散播本软体,惟散播时必须完整保留版权声明及软体授权、且一旦经过修改便不可以再继续使用威注音的产品名称。 +本专案采用 3-Clause BSD License 释出:使用者可自由使用、散播本软件,惟散播时必须完整保留版权声明及软件授权、且一旦经过修改便不可以再继续使用威注音的产品名称。 -- Gitee From a794b5ca4abdeb5fdc7322edbd016a1702dbf0cc Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 27 Jan 2022 11:25:08 +0800 Subject: [PATCH 127/163] Use ObjCpp for Mandarin Module --- .../OpenVanilla/Mandarin/{Mandarin.h => Mandarin.hh} | 2 +- .../Mandarin/{Mandarin.cpp => Mandarin.mm} | 4 ++-- Source/InputMethodController.h | 2 +- vChewing.xcodeproj/project.pbxproj | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) rename Source/3rdParty/OpenVanilla/Mandarin/{Mandarin.h => Mandarin.hh} (99%) rename Source/3rdParty/OpenVanilla/Mandarin/{Mandarin.cpp => Mandarin.mm} (99%) diff --git a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.h b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.hh similarity index 99% rename from Source/3rdParty/OpenVanilla/Mandarin/Mandarin.h rename to Source/3rdParty/OpenVanilla/Mandarin/Mandarin.hh index 8cac6dcdd..102c19efe 100644 --- a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.h +++ b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.hh @@ -1,5 +1,5 @@ /* - * Mandarin.h + * Mandarin.hh * * Copyright 2011-2022 OpenVanilla Project (MIT License). * All rights reserved. See "LICENSE.TXT" for details. diff --git a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.cpp b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.mm similarity index 99% rename from Source/3rdParty/OpenVanilla/Mandarin/Mandarin.cpp rename to Source/3rdParty/OpenVanilla/Mandarin/Mandarin.mm index 5c0881ff0..8e7494f96 100644 --- a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.cpp +++ b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.mm @@ -1,5 +1,5 @@ /* - * Mandarin.cpp + * Mandarin.mm * * Copyright 2011-2022 OpenVanilla Project (MIT License). * All rights reserved. See "LICENSE.TXT" for details. @@ -7,7 +7,7 @@ #include #include -#include "Mandarin.h" +#include "Mandarin.hh" #include "OVUTF8Helper.h" #include "OVWildcard.h" diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index 314038546..75987ec23 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -8,7 +8,7 @@ #import #import -#import "Mandarin.h" +#import "Mandarin.hh" #import "Gramambular.h" #import "vChewingLM.h" #import "UserOverrideModel.h" diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 82d8da374..1bbc670a6 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -55,7 +55,7 @@ 6A0D4ED315FC0D6400ABF4B3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC815FC0D6400ABF4B3 /* main.swift */; }; 6A0D4F0815FC0DA600ABF4B3 /* Bopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EEF15FC0DA600ABF4B3 /* Bopomofo.tiff */; }; 6A0D4F0915FC0DA600ABF4B3 /* Bopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4EF015FC0DA600ABF4B3 /* Bopomofo@2x.tiff */; }; - 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; }; + 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.mm */; }; 6A0D4F5315FC0EE100ABF4B3 /* preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4E15FC0EE100ABF4B3 /* preferences.xib */; }; 6A0D4F5715FC0EF900ABF4B3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4815FC0EE100ABF4B3 /* InfoPlist.strings */; }; 6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6A0D4F4A15FC0EE100ABF4B3 /* Localizable.strings */; }; @@ -195,8 +195,8 @@ 6A0D4F1C15FC0EB100ABF4B3 /* Span.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Span.h; sourceTree = ""; }; 6A0D4F1D15FC0EB100ABF4B3 /* Unigram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Unigram.h; sourceTree = ""; }; 6A0D4F1E15FC0EB100ABF4B3 /* Walker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Walker.h; sourceTree = ""; }; - 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Mandarin.cpp; sourceTree = ""; }; - 6A0D4F2115FC0EB100ABF4B3 /* Mandarin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Mandarin.h; sourceTree = ""; }; + 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Mandarin.mm; sourceTree = ""; }; + 6A0D4F2115FC0EB100ABF4B3 /* Mandarin.hh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Mandarin.hh; sourceTree = ""; }; 6A0D4F4115FC0EB100ABF4B3 /* OVUTF8Helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVUTF8Helper.h; sourceTree = ""; }; 6A0D4F4215FC0EB100ABF4B3 /* OVWildcard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVWildcard.h; sourceTree = ""; }; 6A0D4F4915FC0EE100ABF4B3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -476,8 +476,8 @@ 6A0D4F1F15FC0EB100ABF4B3 /* Mandarin */ = { isa = PBXGroup; children = ( - 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */, - 6A0D4F2115FC0EB100ABF4B3 /* Mandarin.h */, + 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.mm */, + 6A0D4F2115FC0EB100ABF4B3 /* Mandarin.hh */, ); path = Mandarin; sourceTree = ""; @@ -726,7 +726,7 @@ 5BDD25F7279D6D1200AA18F8 /* unzip.m in Sources */, 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, - 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, + 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.mm in Sources */, 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */, 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */, -- Gitee From 5412ab66ba15b21854fb643abdae3c9bab450b19 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 27 Jan 2022 21:40:18 +0800 Subject: [PATCH 128/163] Deprecating EOF Fix in FastLM. - Its mission is complete. All user-editable LM files are not parsed by FastLM. --- Source/Engine/LanguageModel/FastLM.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Source/Engine/LanguageModel/FastLM.cpp b/Source/Engine/LanguageModel/FastLM.cpp index 66b9418b2..acdc9bd6d 100644 --- a/Source/Engine/LanguageModel/FastLM.cpp +++ b/Source/Engine/LanguageModel/FastLM.cpp @@ -36,22 +36,6 @@ bool FastLM::open(const char *path) return false; } - fstream zfd(path); - zfd.seekg(-1,ios_base::end); - char z; - zfd.get(z); - if(z!='\n'){ - syslog(LOG_CONS, "REPORT: Core Language Data file is not ended with a new line.\n"); - syslog(LOG_CONS, "PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); - ofstream zfdo(path, std::ios_base::app); - zfdo << std::endl; - zfdo.close(); - if (zfdo.fail()) { - syslog(LOG_CONS, "REPORT: Failed to append a newline to the data file. Insufficient Privileges?\n"); - return false; - } - } - fd = ::open(path, O_RDONLY); if (fd == -1) { return false; -- Gitee From 44748c21539f830a8e584b2ecc9cc3890fbc070c Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 28 Jan 2022 12:16:03 +0800 Subject: [PATCH 129/163] Use ObjCpp for all LM processors. --- .../LanguageModel/{CNSLM.cpp => CNSLM.mm} | 2 +- .../LanguageModel/{FastLM.cpp => FastLM.mm} | 2 +- ...acementMap.cpp => PhraseReplacementMap.mm} | 2 +- ...OverrideModel.cpp => UserOverrideModel.mm} | 2 +- .../{UserPhrasesLM.cpp => UserPhrasesLM.mm} | 2 +- .../{vChewingLM.cpp => vChewingLM.mm} | 2 +- vChewing.xcodeproj/project.pbxproj | 48 +++++++++---------- 7 files changed, 30 insertions(+), 30 deletions(-) rename Source/Engine/LanguageModel/{CNSLM.cpp => CNSLM.mm} (99%) rename Source/Engine/LanguageModel/{FastLM.cpp => FastLM.mm} (99%) rename Source/Engine/LanguageModel/{PhraseReplacementMap.cpp => PhraseReplacementMap.mm} (98%) rename Source/Engine/LanguageModel/{UserOverrideModel.cpp => UserOverrideModel.mm} (99%) rename Source/Engine/LanguageModel/{UserPhrasesLM.cpp => UserPhrasesLM.mm} (99%) rename Source/Engine/LanguageModel/{vChewingLM.cpp => vChewingLM.mm} (99%) diff --git a/Source/Engine/LanguageModel/CNSLM.cpp b/Source/Engine/LanguageModel/CNSLM.mm similarity index 99% rename from Source/Engine/LanguageModel/CNSLM.cpp rename to Source/Engine/LanguageModel/CNSLM.mm index 6c184821f..970cff481 100644 --- a/Source/Engine/LanguageModel/CNSLM.cpp +++ b/Source/Engine/LanguageModel/CNSLM.mm @@ -1,5 +1,5 @@ /* - * CNSLM.cpp + * CNSLM.mm * * Copyright 2021-2022 vChewing Project (3-Clause BSD License). * Derived from 2011-2022 OpenVanilla Project (MIT License). diff --git a/Source/Engine/LanguageModel/FastLM.cpp b/Source/Engine/LanguageModel/FastLM.mm similarity index 99% rename from Source/Engine/LanguageModel/FastLM.cpp rename to Source/Engine/LanguageModel/FastLM.mm index acdc9bd6d..fc90914ba 100644 --- a/Source/Engine/LanguageModel/FastLM.cpp +++ b/Source/Engine/LanguageModel/FastLM.mm @@ -1,5 +1,5 @@ /* - * FastLM.cpp + * FastLM.mm * * Copyright 2021-2022 vChewing Project (3-Clause BSD License). * Derived from 2011-2022 OpenVanilla Project (MIT License). diff --git a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp b/Source/Engine/LanguageModel/PhraseReplacementMap.mm similarity index 98% rename from Source/Engine/LanguageModel/PhraseReplacementMap.cpp rename to Source/Engine/LanguageModel/PhraseReplacementMap.mm index 0374100e8..3775cd893 100644 --- a/Source/Engine/LanguageModel/PhraseReplacementMap.cpp +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.mm @@ -1,5 +1,5 @@ /* - * PhraseReplacementMap.cpp + * PhraseReplacementMap.mm * * Copyright 2021-2022 vChewing Project (3-Clause BSD License). * Derived from 2011-2022 OpenVanilla Project (MIT License). diff --git a/Source/Engine/LanguageModel/UserOverrideModel.cpp b/Source/Engine/LanguageModel/UserOverrideModel.mm similarity index 99% rename from Source/Engine/LanguageModel/UserOverrideModel.cpp rename to Source/Engine/LanguageModel/UserOverrideModel.mm index 1f8070505..9565921ab 100644 --- a/Source/Engine/LanguageModel/UserOverrideModel.cpp +++ b/Source/Engine/LanguageModel/UserOverrideModel.mm @@ -1,5 +1,5 @@ /* - * UserOverrideModel.cpp + * UserOverrideModel.mm * * Copyright 2021-2022 vChewing Project (3-Clause BSD License). * Derived from 2011-2022 OpenVanilla Project (MIT License). diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.cpp b/Source/Engine/LanguageModel/UserPhrasesLM.mm similarity index 99% rename from Source/Engine/LanguageModel/UserPhrasesLM.cpp rename to Source/Engine/LanguageModel/UserPhrasesLM.mm index 733a371d2..a57dacc30 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.cpp +++ b/Source/Engine/LanguageModel/UserPhrasesLM.mm @@ -1,5 +1,5 @@ /* - * UserPhrasesLM.cpp + * UserPhrasesLM.mm * * Copyright 2021-2022 vChewing Project (3-Clause BSD License). * Derived from 2011-2022 OpenVanilla Project (MIT License). diff --git a/Source/Engine/LanguageModel/vChewingLM.cpp b/Source/Engine/LanguageModel/vChewingLM.mm similarity index 99% rename from Source/Engine/LanguageModel/vChewingLM.cpp rename to Source/Engine/LanguageModel/vChewingLM.mm index 6eae0f476..a0f8ee13b 100644 --- a/Source/Engine/LanguageModel/vChewingLM.cpp +++ b/Source/Engine/LanguageModel/vChewingLM.mm @@ -1,5 +1,5 @@ /* - * vChewingLM.cpp + * vChewingLM.mm * * Copyright 2021-2022 vChewing Project (3-Clause BSD License). * Derived from 2011-2022 OpenVanilla Project (MIT License). diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 1bbc670a6..120bdebaf 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -14,11 +14,11 @@ 5B21711E279B9AD900F91A2B /* ArchiveUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B21711C279B9AD700F91A2B /* ArchiveUtil.swift */; }; 5B217126279BA37500F91A2B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B217124279BA37300F91A2B /* AppDelegate.swift */; }; 5B217128279BB22700F91A2B /* frmAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B217127279BB22700F91A2B /* frmAboutWindow.swift */; }; - 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */; }; + 5B42B64027876FDC00BB9B9F /* UserOverrideModel.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.mm */; }; 5B58E87F278413E7003EA2AD /* BSDLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* BSDLicense.txt */; }; - 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */; }; + 5B5F4F8E27928F9300922DC2 /* vChewingLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F8D27928F9300922DC2 /* vChewingLM.mm */; }; 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */; }; - 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */; }; + 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.mm */; }; 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */; }; 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; }; @@ -31,7 +31,7 @@ 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D19927943D390008F48E /* clsSFX.swift */; }; 5BD0D19F279454F60008F48E /* Beep.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19E279454F60008F48E /* Beep.aif */; }; 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */; }; - 5BD13F482794F0A6000E429F /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */; }; + 5BD13F482794F0A6000E429F /* PhraseReplacementMap.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B42794822C004AC7CE /* PhraseReplacementMap.mm */; }; 5BDD25F2279D65CC00AA18F8 /* UNICHARS.zip in Resources */ = {isa = PBXBuildFile; fileRef = 5BDD25F1279D65CB00AA18F8 /* UNICHARS.zip */; }; 5BDD25F4279D678600AA18F8 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BDD25F3279D677F00AA18F8 /* libz.tbd */; }; 5BDD25F5279D6CFE00AA18F8 /* AWFileHash.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E3279D64FB00AA18F8 /* AWFileHash.m */; }; @@ -40,7 +40,7 @@ 5BDD25F8279D6D1200AA18F8 /* zip.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E7279D64FB00AA18F8 /* zip.m */; }; 5BDD25F9279D6D1200AA18F8 /* ioapi.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E8279D64FB00AA18F8 /* ioapi.m */; }; 5BDD25FA279D6D1200AA18F8 /* mztools.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25E9279D64FB00AA18F8 /* mztools.m */; }; - 5BDD25FD279D6D6300AA18F8 /* CNSLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25FB279D6D6200AA18F8 /* CNSLM.cpp */; }; + 5BDD25FD279D6D6300AA18F8 /* CNSLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5BDD25FB279D6D6200AA18F8 /* CNSLM.mm */; }; 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2CFF2791BECC00838ADB /* InputSourceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */; }; 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */; }; @@ -49,7 +49,7 @@ 5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE798A32792E58A00337FF9 /* TooltipController.swift */; }; 5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE798A62793280C00337FF9 /* NotifierController.swift */; }; 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; - 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; + 6A0421A815FEF3F50061ED63 /* FastLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.mm */; }; 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */; }; 6A0D4ED315FC0D6400ABF4B3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC815FC0D6400ABF4B3 /* main.swift */; }; @@ -103,7 +103,7 @@ 5B21711C279B9AD700F91A2B /* ArchiveUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveUtil.swift; sourceTree = ""; }; 5B217124279BA37300F91A2B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5B217127279BB22700F91A2B /* frmAboutWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = frmAboutWindow.swift; sourceTree = ""; }; - 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = ""; }; + 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = UserOverrideModel.mm; sourceTree = ""; }; 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; 5B42B64127877D6500BB9B9F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/preferences.strings"; sourceTree = ""; }; 5B42B64227877D7700BB9B9F /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "Source/zh-Hant.lproj/preferences.strings"; sourceTree = ""; }; @@ -111,13 +111,13 @@ 5B58E880278413EF003EA2AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hans"; path = "zh-Hans.lproj/BSDLicense.txt"; sourceTree = ""; }; 5B58E881278413F1003EA2AD /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/BSDLicense.txt"; sourceTree = ""; }; 5B5F4F8C27928F9300922DC2 /* vChewingLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vChewingLM.h; sourceTree = ""; }; - 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = vChewingLM.cpp; sourceTree = ""; }; + 5B5F4F8D27928F9300922DC2 /* vChewingLM.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = vChewingLM.mm; sourceTree = ""; }; 5B5F4F91279294A300922DC2 /* LanguageModelManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LanguageModelManager.h; sourceTree = ""; }; 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = ""; }; 5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPhrasesLM.h; sourceTree = ""; }; - 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UserPhrasesLM.cpp; sourceTree = ""; }; + 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = UserPhrasesLM.mm; sourceTree = ""; }; 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = ""; }; - 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = ""; }; + 5B6797B42794822C004AC7CE /* PhraseReplacementMap.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PhraseReplacementMap.mm; sourceTree = ""; }; 5B9781D32763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -159,7 +159,7 @@ 5BDD25F0279D64FB00AA18F8 /* SSZipArchive.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SSZipArchive.m; sourceTree = ""; }; 5BDD25F1279D65CB00AA18F8 /* UNICHARS.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; name = UNICHARS.zip; path = Data/components/common/UNICHARS.zip; sourceTree = ""; }; 5BDD25F3279D677F00AA18F8 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - 5BDD25FB279D6D6200AA18F8 /* CNSLM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CNSLM.cpp; sourceTree = ""; }; + 5BDD25FB279D6D6200AA18F8 /* CNSLM.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CNSLM.mm; sourceTree = ""; }; 5BDD25FC279D6D6300AA18F8 /* CNSLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CNSLM.h; sourceTree = ""; }; 5BDF2CFD2791BE4400838ADB /* InputSourceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceHelper.swift; sourceTree = ""; }; 5BDF2D002791C03B00838ADB /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; @@ -171,7 +171,7 @@ 5BF4A70527844DD2007DC6E7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmAboutWindow.strings; sourceTree = ""; }; 5BF4A70727844DD3007DC6E7 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = ""; }; 5BF4A70A278451A6007DC6E7 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/frmAboutWindow.strings"; sourceTree = ""; }; - 6A0421A615FEF3F50061ED63 /* FastLM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FastLM.cpp; sourceTree = ""; }; + 6A0421A615FEF3F50061ED63 /* FastLM.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FastLM.mm; sourceTree = ""; }; 6A0421A715FEF3F50061ED63 /* FastLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FastLM.h; sourceTree = ""; }; 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewing.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; @@ -249,17 +249,17 @@ 5BA8DAFE27928120009C9FFF /* LanguageModel */ = { isa = PBXGroup; children = ( - 5BDD25FB279D6D6200AA18F8 /* CNSLM.cpp */, + 5BDD25FB279D6D6200AA18F8 /* CNSLM.mm */, 5BDD25FC279D6D6300AA18F8 /* CNSLM.h */, - 5B5F4F8D27928F9300922DC2 /* vChewingLM.cpp */, + 5B5F4F8D27928F9300922DC2 /* vChewingLM.mm */, 5B5F4F8C27928F9300922DC2 /* vChewingLM.h */, - 6A0421A615FEF3F50061ED63 /* FastLM.cpp */, + 6A0421A615FEF3F50061ED63 /* FastLM.mm */, 6A0421A715FEF3F50061ED63 /* FastLM.h */, - 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */, + 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.mm */, 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */, - 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.cpp */, + 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.mm */, 5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */, - 5B6797B42794822C004AC7CE /* PhraseReplacementMap.cpp */, + 5B6797B42794822C004AC7CE /* PhraseReplacementMap.mm */, 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */, ); path = LanguageModel; @@ -715,29 +715,29 @@ 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.swift in Sources */, - 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.cpp in Sources */, + 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.mm in Sources */, 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */, - 5B5F4F8E27928F9300922DC2 /* vChewingLM.cpp in Sources */, + 5B5F4F8E27928F9300922DC2 /* vChewingLM.mm in Sources */, 5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */, 5BDD25F6279D6D0200AA18F8 /* SSZipArchive.m in Sources */, 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, - 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, + 5B42B64027876FDC00BB9B9F /* UserOverrideModel.mm in Sources */, 5BDD25F7279D6D1200AA18F8 /* unzip.m in Sources */, 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.mm in Sources */, 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */, - 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */, + 6A0421A815FEF3F50061ED63 /* FastLM.mm in Sources */, 5BDF2D012791C03B00838ADB /* PreferencesWindowController.swift in Sources */, 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */, 5B217128279BB22700F91A2B /* frmAboutWindow.swift in Sources */, - 5BD13F482794F0A6000E429F /* PhraseReplacementMap.cpp in Sources */, + 5BD13F482794F0A6000E429F /* PhraseReplacementMap.mm in Sources */, 5BDD25FA279D6D1200AA18F8 /* mztools.m in Sources */, 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, - 5BDD25FD279D6D6300AA18F8 /* CNSLM.cpp in Sources */, + 5BDD25FD279D6D6300AA18F8 /* CNSLM.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -- Gitee From ecee3ad7bf36efc0f5adce4618a571d983e1b81e Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 28 Jan 2022 13:23:31 +0800 Subject: [PATCH 130/163] Deprecating Zonble's on-write EOF fixer. - Zonble thinks that his idea is superior than mine. However, his design, by putting an EOF fixer into the writeUserPhrase section, is totally meaningless: EOF fixer is for fixing manually-introduced EOF faults since automatically-inserted UserPhrase entries never generate EOF problems. --- Source/LanguageModelManager.mm | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 37204e57e..7ab0ff8aa 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -166,31 +166,9 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return NO; } - BOOL shouldAddLineBreakAtFront = NO; NSString *path = [self userPhrasesDataPath:inputMode]; - if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { - NSError *error = nil; - NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; - unsigned long long fileSize = [attr fileSize]; - if (!error && fileSize) { - NSFileHandle *readFile = [NSFileHandle fileHandleForReadingAtPath:path]; - if (readFile) { - [readFile seekToFileOffset:fileSize - 1]; - NSData *data = [readFile readDataToEndOfFile]; - const void *bytes = [data bytes]; - if (*(char *)bytes != '\n') { - shouldAddLineBreakAtFront = YES; - } - [readFile closeFile]; - } - } - } - NSMutableString *currentMarkedPhrase = [NSMutableString string]; - if (shouldAddLineBreakAtFront) { - [currentMarkedPhrase appendString:@"\n"]; - } [currentMarkedPhrase appendString:userPhrase]; [currentMarkedPhrase appendString:@"\n"]; -- Gitee From 502644af9cd286cf869744f10f63eba6eef206f7 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 28 Jan 2022 12:22:40 +0800 Subject: [PATCH 131/163] LMConsolidator // Module Implementation. - We aren't like those cowards living in the upstream who prefer to make LM modules "tolerant". We actively consolidate user-editable files to fix common user-generated mistakes and duplicated entries. - The LMConsolidator has an independent EOF fixer and a comprehensive Content-Consolidator. The Content-Consolidator receives a parameter to decide whether it should sort the contents in the language model file, 'cause some users may prefer their own content sequences in their editable language model files. - We don't introduce HYPY2BPMF conversion module this time until we can find a good solution. --- Source/Engine/LanguageModel/LMConsolidator.h | 32 +++++++ Source/Engine/LanguageModel/LMConsolidator.mm | 87 +++++++++++++++++++ .../LanguageModel/PhraseReplacementMap.mm | 20 +---- Source/Engine/LanguageModel/UserPhrasesLM.mm | 19 +--- vChewing.xcodeproj/project.pbxproj | 6 ++ 5 files changed, 132 insertions(+), 32 deletions(-) create mode 100644 Source/Engine/LanguageModel/LMConsolidator.h create mode 100644 Source/Engine/LanguageModel/LMConsolidator.mm diff --git a/Source/Engine/LanguageModel/LMConsolidator.h b/Source/Engine/LanguageModel/LMConsolidator.h new file mode 100644 index 000000000..6ea8faea9 --- /dev/null +++ b/Source/Engine/LanguageModel/LMConsolidator.h @@ -0,0 +1,32 @@ +/* + * LMConsolidator.h + * vChewing-Specific module for Consolidating Language Model Data files. + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +#ifndef LMConsolidator_hpp +#define LMConsolidator_hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +namespace vChewing { + +class LMConsolidator +{ +public: + static bool FixEOF(const char *path); + static bool ConsolidateContent(const char *path, bool shouldsort); +}; + +} // namespace vChewing +#endif /* LMConsolidator_hpp */ diff --git a/Source/Engine/LanguageModel/LMConsolidator.mm b/Source/Engine/LanguageModel/LMConsolidator.mm new file mode 100644 index 000000000..bfba1f805 --- /dev/null +++ b/Source/Engine/LanguageModel/LMConsolidator.mm @@ -0,0 +1,87 @@ +/* + * LMConsolidator.mm + * vChewing-Specific module for Consolidating Language Model Data files. + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +#include "LMConsolidator.h" + +namespace vChewing { + +// EOF FIXER. CREDIT: Shiki Suen. +bool LMConsolidator::FixEOF(const char *path) +{ + std::fstream zfdEOFFixerIncomingStream(path); + zfdEOFFixerIncomingStream.seekg(-1,std::ios_base::end); + char z; + zfdEOFFixerIncomingStream.get(z); + if(z!='\n'){ + syslog(LOG_CONS, "// REPORT: Data File not ended with a new line.\n"); + syslog(LOG_CONS, "// DATA FILE: %s", path); + syslog(LOG_CONS, "// PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); + std::ofstream zfdEOFFixerOutput(path, std::ios_base::app); + zfdEOFFixerOutput << std::endl; + zfdEOFFixerOutput.close(); + if (zfdEOFFixerOutput.fail()) { + syslog(LOG_CONS, "// REPORT: Failed to append a newline to the data file. Insufficient Privileges?\n"); + syslog(LOG_CONS, "// DATA FILE: %s", path); + return false; + } + } + zfdEOFFixerIncomingStream.close(); + if (zfdEOFFixerIncomingStream.fail()) { + syslog(LOG_CONS, "// REPORT: Failed to read lines through the data file for EOF check. Insufficient Privileges?\n"); + syslog(LOG_CONS, "// DATA FILE: %s", path); + return false; + } + return true; +} // END: EOF FIXER. + +// CONTENT CONSOLIDATOR. CREDIT: Shiki Suen. +bool LMConsolidator::ConsolidateContent(const char *path, bool shouldsort) { + ifstream zfdContentConsolidatorIncomingStream(path); + vectorvecEntry; + while(!zfdContentConsolidatorIncomingStream.eof()) + { // Xcode 13 能用的 ObjCpp 與 Cpp 並無原生支援「\h」這個 Regex 參數的能力,只能逐行處理。 + string zfdBuffer; + getline(zfdContentConsolidatorIncomingStream,zfdBuffer); + vecEntry.push_back(zfdBuffer); + } + // 第一遍 for 用來統整每行內的內容。 + regex sedCJKWhiteSpace(" "), sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // RegEx 先定義好。 + for(int i=0;i #include "KeyValueBlobReader.h" #include "PhraseReplacementMap.h" +#include "LMConsolidator.h" namespace vChewing { @@ -38,22 +39,9 @@ bool PhraseReplacementMap::open(const char *path) if (data) { return false; } - - std::fstream zfd(path); - zfd.seekg(-1,std::ios_base::end); - char z; - zfd.get(z); - if(z!='\n'){ - syslog(LOG_CONS, "REPORT: Phrase Replacement Map File is not ended with a new line.\n"); - syslog(LOG_CONS, "PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); - std::ofstream zfdo(path, std::ios_base::app); - zfdo << std::endl; - zfdo.close(); - if (zfdo.fail()) { - syslog(LOG_CONS, "REPORT: Failed to append a newline to the data file. Insufficient Privileges?\n"); - return false; - } - } + + LMConsolidator::FixEOF(path); + LMConsolidator::ConsolidateContent(path, false); fd = ::open(path, O_RDONLY); if (fd == -1) { diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.mm b/Source/Engine/LanguageModel/UserPhrasesLM.mm index a57dacc30..fe94a305b 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.mm +++ b/Source/Engine/LanguageModel/UserPhrasesLM.mm @@ -13,7 +13,7 @@ #include #include #include - +#include "LMConsolidator.h" #include "KeyValueBlobReader.h" namespace vChewing { @@ -38,21 +38,8 @@ bool UserPhrasesLM::open(const char *path) return false; } - std::fstream zfd(path); - zfd.seekg(-1,std::ios_base::end); - char z; - zfd.get(z); - if(z!='\n'){ - syslog(LOG_CONS, "REPORT: User Phrase Data File is not ended with a new line.\n"); - syslog(LOG_CONS, "PROCEDURE: Trying to insert a new line as EOF before per-line check process.\n"); - std::ofstream zfdo(path, std::ios_base::app); - zfdo << std::endl; - zfdo.close(); - if (zfdo.fail()) { - syslog(LOG_CONS, "REPORT: Failed to append a newline to the data file. Insufficient Privileges?\n"); - return false; - } - } + LMConsolidator::FixEOF(path); + LMConsolidator::ConsolidateContent(path, false); fd = ::open(path, O_RDONLY); if (fd == -1) { diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 120bdebaf..d171ee00e 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F92279294A300922DC2 /* LanguageModelManager.mm */; }; 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.mm */; }; 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */; }; + 5B810D9F27A3A5E50032C1A9 /* LMConsolidator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B810D9D27A3A5E50032C1A9 /* LMConsolidator.mm */; }; 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; }; 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */; }; @@ -118,6 +119,8 @@ 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = UserPhrasesLM.mm; sourceTree = ""; }; 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhraseReplacementMap.h; sourceTree = ""; }; 5B6797B42794822C004AC7CE /* PhraseReplacementMap.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PhraseReplacementMap.mm; sourceTree = ""; }; + 5B810D9D27A3A5E50032C1A9 /* LMConsolidator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LMConsolidator.mm; sourceTree = ""; }; + 5B810D9E27A3A5E50032C1A9 /* LMConsolidator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMConsolidator.h; sourceTree = ""; }; 5B9781D32763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -261,6 +264,8 @@ 5B5F4F952792A4EA00922DC2 /* UserPhrasesLM.h */, 5B6797B42794822C004AC7CE /* PhraseReplacementMap.mm */, 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */, + 5B810D9D27A3A5E50032C1A9 /* LMConsolidator.mm */, + 5B810D9E27A3A5E50032C1A9 /* LMConsolidator.h */, ); path = LanguageModel; sourceTree = ""; @@ -734,6 +739,7 @@ 5B217128279BB22700F91A2B /* frmAboutWindow.swift in Sources */, 5BD13F482794F0A6000E429F /* PhraseReplacementMap.mm in Sources */, 5BDD25FA279D6D1200AA18F8 /* mztools.m in Sources */, + 5B810D9F27A3A5E50032C1A9 /* LMConsolidator.mm in Sources */, 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, -- Gitee From 3eecdca587535866dee252fcfaebd45fe1836dfd Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 29 Jan 2022 17:51:21 +0800 Subject: [PATCH 132/163] LMConsolidator // Regex Patch for proper match. - Previously it doesn't match non-break whitespace. --- Source/Engine/LanguageModel/LMConsolidator.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Engine/LanguageModel/LMConsolidator.mm b/Source/Engine/LanguageModel/LMConsolidator.mm index bfba1f805..8e6593c14 100644 --- a/Source/Engine/LanguageModel/LMConsolidator.mm +++ b/Source/Engine/LanguageModel/LMConsolidator.mm @@ -49,11 +49,13 @@ bool LMConsolidator::ConsolidateContent(const char *path, bool shouldsort) { vecEntry.push_back(zfdBuffer); } // 第一遍 for 用來統整每行內的內容。 - regex sedCJKWhiteSpace(" "), sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // RegEx 先定義好。 + // regex sedCJKWhiteSpace("\\x{3000}"), sedNonBreakWhiteSpace("\\x{A0}"), sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // 這樣寫會導致輸入法敲不了任何字,推測 Xcode 13 支援的 cpp / objCpp 可能對某些 Regex 寫法有相容性問題。 + regex sedCJKWhiteSpace(" "), sedNonBreakWhiteSpace(" "), sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // RegEx 先定義好。 for(int i=0;i Date: Sat, 29 Jan 2022 15:04:21 +0800 Subject: [PATCH 133/163] Pref // Merging OOBE module into Pref module. --- Source/AppDelegate.swift | 2 +- Source/Engine/vChewing/clsOOBEDefaults.swift | 80 ---------- Source/InputMethodController.mm | 4 +- Source/PreferencesModule.swift | 159 +++++++++++++------ vChewing.xcodeproj/project.pbxproj | 4 - 5 files changed, 111 insertions(+), 138 deletions(-) delete mode 100644 Source/Engine/vChewing/clsOOBEDefaults.swift diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index c84206f86..0959b114f 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -150,7 +150,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle LanguageModelManager.loadUserPhrases() LanguageModelManager.loadUserPhraseReplacement() - OOBE.setMissingDefaults() + Preferences.setMissingDefaults() // 只要使用者沒有勾選檢查更新、沒有主動做出要檢查更新的操作,就不要檢查更新。 if (UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) != nil) == true { diff --git a/Source/Engine/vChewing/clsOOBEDefaults.swift b/Source/Engine/vChewing/clsOOBEDefaults.swift deleted file mode 100644 index 8e82b9c56..000000000 --- a/Source/Engine/vChewing/clsOOBEDefaults.swift +++ /dev/null @@ -1,80 +0,0 @@ -/* - * clsOOBEDefaults.swift - * - * Copyright 2021-2022 vChewing Project (3-Clause BSD License). - * Derived from 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -import Cocoa - -private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" -private let kCheckUpdateAutomatically = "CheckUpdateAutomatically" -private let kCandidateListTextSize = "CandidateListTextSize" -private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" -private let kUseWinNT351BPMF = "UseWinNT351BPMF" -private let kSelectPhraseAfterCursorAsCandidate = "SelectPhraseAfterCursorAsCandidate" -private let kUseHorizontalCandidateList = "UseHorizontalCandidateList" -private let kCNS11643EnabledKey = "CNS11643Enabled" -private let kChineseConversionEnabledKey = "ChineseConversionEnabled" -private let kPhraseReplacementEnabledKey = "PhraseReplacementEnabled" - -@objc public class OOBE : NSObject { - - @objc public static func setMissingDefaults () { - // 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。 - - // 首次啟用輸入法時設定不要自動更新,免得在某些要隔絕外部網路連線的保密機構內觸犯資安規則。 - if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { - UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically) - UserDefaults.standard.synchronize() - } - - // 預設選字窗字詞文字尺寸,設成 18 剛剛好 - if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil { - UserDefaults.standard.set(Preferences.candidateListTextSize, forKey: kCandidateListTextSize) - } - - // 預設摁空格鍵來選字,所以設成 true - if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpaceKey) == nil { - UserDefaults.standard.set(Preferences.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpaceKey) - } - - // 預設禁用 WinNT351 風格的注音選字模式(就是每個字都要選的那種),所以設成 false - if UserDefaults.standard.object(forKey: kUseWinNT351BPMF) == nil { - UserDefaults.standard.set(Preferences.useWinNT351BPMF, forKey: kUseWinNT351BPMF) - } - - // 預設漢音風格選字,所以要設成 0 - if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidate) == nil { - UserDefaults.standard.set(Preferences.selectPhraseAfterCursorAsCandidate, forKey: kSelectPhraseAfterCursorAsCandidate) - } - - // 預設橫向選字窗,不爽請自行改成縱向選字窗 - if UserDefaults.standard.object(forKey: kUseHorizontalCandidateList) == nil { - UserDefaults.standard.set(Preferences.useHorizontalCandidateList, forKey: kUseHorizontalCandidateList) - } - - // 預設停用全字庫支援 - if UserDefaults.standard.object(forKey: kCNS11643EnabledKey) == nil { - UserDefaults.standard.set(Preferences.cns11643Enabled, forKey: kCNS11643EnabledKey) - } - - // 預設停用繁體轉康熙模組 - if UserDefaults.standard.object(forKey: kChineseConversionEnabledKey) == nil { - UserDefaults.standard.set(Preferences.chineseConversionEnabled, forKey: kChineseConversionEnabledKey) - } - - // 預設停用自訂語彙置換 - if UserDefaults.standard.object(forKey: kPhraseReplacementEnabledKey) == nil { - UserDefaults.standard.set(Preferences.phraseReplacementEnabled, forKey: kPhraseReplacementEnabledKey) - } - - // 預設沒事不要在那裡放屁 - if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil { - UserDefaults.standard.set(Preferences.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) - } - - UserDefaults.standard.synchronize() - } -} diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 6529b2f0f..7f3303c56 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -182,7 +182,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)activateServer:(id)client { // Write missing OOBE user plist entries. - [OOBE setMissingDefaults]; + [Preferences setMissingDefaults]; // Read user plist. [[NSUserDefaults standardUserDefaults] synchronize]; @@ -1493,7 +1493,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)showPreferences:(id)sender { // Write missing OOBE user plist entries. - [OOBE setMissingDefaults]; + [Preferences setMissingDefaults]; // show the preferences panel, and also make the IME app itself the focus if ([IMKInputController instancesRespondToSelector:@selector(showPreferences:)]) { diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index cd6267df4..593c27889 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -8,20 +8,21 @@ import Cocoa -private let kKeyboardLayoutPreferenceKey = "KeyboardLayout" -private let kBasisKeyboardLayoutPreferenceKey = "BasisKeyboardLayout" // alphanumeric ("ASCII") input basi -private let kFunctionKeyKeyboardLayoutPreferenceKey = "FunctionKeyKeyboardLayout" // alphanumeric ("ASCII") input basi -private let kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey = "FunctionKeyKeyboardLayoutOverrideIncludeShift" // whether include shif -private let kCandidateListTextSizeKey = "CandidateListTextSize" -private let kAppleLanguagesPreferencesKey = "AppleLanguages" -private let kSelectPhraseAfterCursorAsCandidatePreferenceKey = "SelectPhraseAfterCursorAsCandidate" -private let kUseHorizontalCandidateListPreferenceKey = "UseHorizontalCandidateList" -private let kComposingBufferSizePreferenceKey = "ComposingBufferSize" -private let kChooseCandidateUsingSpaceKey = "ChooseCandidateUsingSpaceKey" -private let kCNS11643EnabledKey = "CNS11643Enabled" -private let kChineseConversionEnabledKey = "ChineseConversionEnabled" -private let kHalfWidthPunctuationEnabledKey = "HalfWidthPunctuationEnable" -private let kEscToCleanInputBufferKey = "EscToCleanInputBuffer" +private let kCheckUpdateAutomatically = "CheckUpdateAutomatically" +private let kKeyboardLayoutPreference = "KeyboardLayout" +private let kBasisKeyboardLayoutPreference = "BasisKeyboardLayout" +private let kFunctionKeyKeyboardLayoutPreference = "FunctionKeyKeyboardLayout" +private let kFunctionKeyKeyboardLayoutOverrideIncludeShift = "FunctionKeyKeyboardLayoutOverrideIncludeShift" +private let kCandidateListTextSize = "CandidateListTextSize" +private let kAppleLanguagesPreferences = "AppleLanguages" +private let kSelectPhraseAfterCursorAsCandidatePreference = "SelectPhraseAfterCursorAsCandidate" +private let kUseHorizontalCandidateListPreference = "UseHorizontalCandidateList" +private let kComposingBufferSizePreference = "ComposingBufferSize" +private let kChooseCandidateUsingSpace = "ChooseCandidateUsingSpaceKey" +private let kCNS11643Enabled = "CNS11643Enabled" +private let kChineseConversionEnabled = "ChineseConversionEnabled" +private let kHalfWidthPunctuationEnabled = "HalfWidthPunctuationEnable" +private let kEscToCleanInputBuffer = "EscToCleanInputBuffer" private let kUseWinNT351BPMF = "UseWinNT351BPMF" private let kMaxCandidateLength = "MaxCandidateLength" private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" @@ -29,8 +30,8 @@ private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" private let kCandidateTextFontName = "CandidateTextFontName" private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" private let kCandidateKeys = "CandidateKeys" -private let kChineseConversionEngineKey = "ChineseConversionEngine" -private let kPhraseReplacementEnabledKey = "PhraseReplacementEnabled" +private let kChineseConversionEngine = "ChineseConversionEngine" +private let kPhraseReplacementEnabled = "PhraseReplacementEnabled" private let kDefaultCandidateListTextSize: CGFloat = 18 private let kMinKeyLabelSize: CGFloat = 10 @@ -168,62 +169,118 @@ struct ComposingBufferSize { @objc public class Preferences: NSObject { static func reset() { let defaults = UserDefaults.standard - defaults.removeObject(forKey: kKeyboardLayoutPreferenceKey) - defaults.removeObject(forKey: kBasisKeyboardLayoutPreferenceKey) - defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutPreferenceKey) - defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey) - defaults.removeObject(forKey: kCandidateListTextSizeKey) - defaults.removeObject(forKey: kAppleLanguagesPreferencesKey) - defaults.removeObject(forKey: kSelectPhraseAfterCursorAsCandidatePreferenceKey) - defaults.removeObject(forKey: kUseHorizontalCandidateListPreferenceKey) - defaults.removeObject(forKey: kComposingBufferSizePreferenceKey) - defaults.removeObject(forKey: kChooseCandidateUsingSpaceKey) - defaults.removeObject(forKey: kCNS11643EnabledKey) - defaults.removeObject(forKey: kChineseConversionEnabledKey) - defaults.removeObject(forKey: kHalfWidthPunctuationEnabledKey) - defaults.removeObject(forKey: kEscToCleanInputBufferKey) + defaults.removeObject(forKey: kKeyboardLayoutPreference) + defaults.removeObject(forKey: kBasisKeyboardLayoutPreference) + defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutPreference) + defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutOverrideIncludeShift) + defaults.removeObject(forKey: kCandidateListTextSize) + defaults.removeObject(forKey: kAppleLanguagesPreferences) + defaults.removeObject(forKey: kSelectPhraseAfterCursorAsCandidatePreference) + defaults.removeObject(forKey: kUseHorizontalCandidateListPreference) + defaults.removeObject(forKey: kComposingBufferSizePreference) + defaults.removeObject(forKey: kChooseCandidateUsingSpace) + defaults.removeObject(forKey: kCNS11643Enabled) + defaults.removeObject(forKey: kChineseConversionEnabled) + defaults.removeObject(forKey: kHalfWidthPunctuationEnabled) + defaults.removeObject(forKey: kEscToCleanInputBuffer) defaults.removeObject(forKey: kCandidateTextFontName) defaults.removeObject(forKey: kCandidateKeyLabelFontName) defaults.removeObject(forKey: kCandidateKeys) - defaults.removeObject(forKey: kPhraseReplacementEnabledKey) - defaults.removeObject(forKey: kChineseConversionEngineKey) + defaults.removeObject(forKey: kPhraseReplacementEnabled) + defaults.removeObject(forKey: kChineseConversionEngine) defaults.removeObject(forKey: kUseWinNT351BPMF) defaults.removeObject(forKey: kMaxCandidateLength) defaults.removeObject(forKey: kShouldNotFartInLieuOfBeep) } - @UserDefault(key: kAppleLanguagesPreferencesKey, defaultValue: []) + @objc public static func setMissingDefaults () { + // 既然 Preferences Module 的預設屬性不自動寫入 plist、而且還是 private,那這邊就先寫入了。 + + // 首次啟用輸入法時設定不要自動更新,免得在某些要隔絕外部網路連線的保密機構內觸犯資安規則。 + if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil { + UserDefaults.standard.set(false, forKey: kCheckUpdateAutomatically) + } + + // 預設選字窗字詞文字尺寸,設成 18 剛剛好 + if UserDefaults.standard.object(forKey: kCandidateListTextSize) == nil { + UserDefaults.standard.set(Preferences.candidateListTextSize, forKey: kCandidateListTextSize) + } + + // 預設摁空格鍵來選字,所以設成 true + if UserDefaults.standard.object(forKey: kChooseCandidateUsingSpace) == nil { + UserDefaults.standard.set(Preferences.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace) + } + + // 預設禁用 WinNT351 風格的注音選字模式(就是每個字都要選的那種),所以設成 false + if UserDefaults.standard.object(forKey: kUseWinNT351BPMF) == nil { + UserDefaults.standard.set(Preferences.useWinNT351BPMF, forKey: kUseWinNT351BPMF) + } + + // 預設漢音風格選字,所以要設成 0 + if UserDefaults.standard.object(forKey: kSelectPhraseAfterCursorAsCandidatePreference) == nil { + UserDefaults.standard.set(Preferences.selectPhraseAfterCursorAsCandidate, forKey: kSelectPhraseAfterCursorAsCandidatePreference) + } + + // 預設橫向選字窗,不爽請自行改成縱向選字窗 + if UserDefaults.standard.object(forKey: kUseHorizontalCandidateListPreference) == nil { + UserDefaults.standard.set(Preferences.useHorizontalCandidateList, forKey: kUseHorizontalCandidateListPreference) + } + + // 預設停用全字庫支援 + if UserDefaults.standard.object(forKey: kCNS11643Enabled) == nil { + UserDefaults.standard.set(Preferences.cns11643Enabled, forKey: kCNS11643Enabled) + } + + // 預設停用繁體轉康熙模組 + if UserDefaults.standard.object(forKey: kChineseConversionEnabled) == nil { + UserDefaults.standard.set(Preferences.chineseConversionEnabled, forKey: kChineseConversionEnabled) + } + + // 預設停用自訂語彙置換 + if UserDefaults.standard.object(forKey: kPhraseReplacementEnabled) == nil { + UserDefaults.standard.set(Preferences.phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) + } + + // 預設沒事不要在那裡放屁 + if UserDefaults.standard.object(forKey: kShouldNotFartInLieuOfBeep) == nil { + UserDefaults.standard.set(Preferences.shouldNotFartInLieuOfBeep, forKey: kShouldNotFartInLieuOfBeep) + } + + UserDefaults.standard.synchronize() + } + + @UserDefault(key: kAppleLanguagesPreferences, defaultValue: []) @objc static var appleLanguages: Array - @UserDefault(key: kKeyboardLayoutPreferenceKey, defaultValue: 0) + @UserDefault(key: kKeyboardLayoutPreference, defaultValue: 0) @objc static var keyboardLayout: Int @objc static var keyboardLayoutName: String { (KeyboardLayout(rawValue: self.keyboardLayout) ?? KeyboardLayout.standard).name } - @UserDefault(key: kBasisKeyboardLayoutPreferenceKey, defaultValue: "com.apple.keylayout.US") + @UserDefault(key: kBasisKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.US") @objc static var basisKeyboardLayout: String - @UserDefault(key: kFunctionKeyKeyboardLayoutPreferenceKey, defaultValue: "com.apple.keylayout.US") + @UserDefault(key: kFunctionKeyKeyboardLayoutPreference, defaultValue: "com.apple.keylayout.US") @objc static var functionKeyboardLayout: String - @UserDefault(key: kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey, defaultValue: false) + @UserDefault(key: kFunctionKeyKeyboardLayoutOverrideIncludeShift, defaultValue: false) @objc static var functionKeyKeyboardLayoutOverrideIncludeShiftKey: Bool - @CandidateListTextSize(key: kCandidateListTextSizeKey) + @CandidateListTextSize(key: kCandidateListTextSize) @objc static var candidateListTextSize: CGFloat - @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreferenceKey, defaultValue: false) + @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false) @objc static var selectPhraseAfterCursorAsCandidate: Bool - @UserDefault(key: kUseHorizontalCandidateListPreferenceKey, defaultValue: true) + @UserDefault(key: kUseHorizontalCandidateListPreference, defaultValue: true) @objc static var useHorizontalCandidateList: Bool - @ComposingBufferSize(key: kComposingBufferSizePreferenceKey) + @ComposingBufferSize(key: kComposingBufferSizePreference) @objc static var composingBufferSize: Int - @UserDefault(key: kChooseCandidateUsingSpaceKey, defaultValue: true) + @UserDefault(key: kChooseCandidateUsingSpace, defaultValue: true) @objc static var chooseCandidateUsingSpace: Bool @UserDefault(key: kUseWinNT351BPMF, defaultValue: false) @@ -247,25 +304,25 @@ struct ComposingBufferSize { return shouldNotFartInLieuOfBeep } - @UserDefault(key: kCNS11643EnabledKey, defaultValue: false) + @UserDefault(key: kCNS11643Enabled, defaultValue: false) @objc static var cns11643Enabled: Bool @objc static func toggleCNS11643Enabled() -> Bool { cns11643Enabled = !cns11643Enabled - UserDefaults.standard.set(cns11643Enabled, forKey: kCNS11643EnabledKey) + UserDefaults.standard.set(cns11643Enabled, forKey: kCNS11643Enabled) return cns11643Enabled } - @UserDefault(key: kChineseConversionEnabledKey, defaultValue: false) + @UserDefault(key: kChineseConversionEnabled, defaultValue: false) @objc static var chineseConversionEnabled: Bool @objc static func toggleChineseConversionEnabled() -> Bool { chineseConversionEnabled = !chineseConversionEnabled - UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabledKey) + UserDefaults.standard.set(chineseConversionEnabled, forKey: kChineseConversionEnabled) return chineseConversionEnabled } - @UserDefault(key: kHalfWidthPunctuationEnabledKey, defaultValue: false) + @UserDefault(key: kHalfWidthPunctuationEnabled, defaultValue: false) @objc static var halfWidthPunctuationEnabled: Bool @objc static func toggleHalfWidthPunctuationEnabled() -> Bool { @@ -273,7 +330,7 @@ struct ComposingBufferSize { return halfWidthPunctuationEnabled } - @UserDefault(key: kEscToCleanInputBufferKey, defaultValue: true) + @UserDefault(key: kEscToCleanInputBuffer, defaultValue: true) @objc static var escToCleanInputBuffer: Bool // MARK: Optional settings @@ -343,19 +400,19 @@ struct ComposingBufferSize { } - @UserDefault(key: kChineseConversionEngineKey, defaultValue: 0) + @UserDefault(key: kChineseConversionEngine, defaultValue: 0) @objc static var chineseConversionEngine: Int @objc static var chineseConversionEngineName: String? { return ChineseConversionEngine(rawValue: chineseConversionEngine)?.name } - @UserDefault(key: kPhraseReplacementEnabledKey, defaultValue: false) + @UserDefault(key: kPhraseReplacementEnabled, defaultValue: false) @objc static var phraseReplacementEnabled: Bool @objc static func togglePhraseReplacementEnabled() -> Bool { phraseReplacementEnabled = !phraseReplacementEnabled - UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabledKey) + UserDefaults.standard.set(phraseReplacementEnabled, forKey: kPhraseReplacementEnabled) return phraseReplacementEnabled } } diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index d171ee00e..79091b13c 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ 5BD0D19427940E9D0008F48E /* Fart.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19327940E9D0008F48E /* Fart.aif */; }; 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D19927943D390008F48E /* clsSFX.swift */; }; 5BD0D19F279454F60008F48E /* Beep.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19E279454F60008F48E /* Beep.aif */; }; - 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */; }; 5BD13F482794F0A6000E429F /* PhraseReplacementMap.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B42794822C004AC7CE /* PhraseReplacementMap.mm */; }; 5BDD25F2279D65CC00AA18F8 /* UNICHARS.zip in Resources */ = {isa = PBXBuildFile; fileRef = 5BDD25F1279D65CB00AA18F8 /* UNICHARS.zip */; }; 5BDD25F4279D678600AA18F8 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BDD25F3279D677F00AA18F8 /* libz.tbd */; }; @@ -146,7 +145,6 @@ 5BD0D19327940E9D0008F48E /* Fart.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.aif; sourceTree = ""; }; 5BD0D19927943D390008F48E /* clsSFX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = clsSFX.swift; sourceTree = ""; }; 5BD0D19E279454F60008F48E /* Beep.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.aif; sourceTree = ""; }; - 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = clsOOBEDefaults.swift; sourceTree = ""; }; 5BDD25E2279D64FB00AA18F8 /* AWFileHash.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AWFileHash.h; sourceTree = ""; }; 5BDD25E3279D64FB00AA18F8 /* AWFileHash.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AWFileHash.m; sourceTree = ""; }; 5BDD25E6279D64FB00AA18F8 /* unzip.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = unzip.m; sourceTree = ""; }; @@ -275,7 +273,6 @@ children = ( 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */, 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */, - 5BD0D1A3279463510008F48E /* clsOOBEDefaults.swift */, ); path = vChewing; sourceTree = ""; @@ -729,7 +726,6 @@ 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.mm in Sources */, 5BDD25F7279D6D1200AA18F8 /* unzip.m in Sources */, - 5BD0D1A4279463510008F48E /* clsOOBEDefaults.swift in Sources */, D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */, 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.mm in Sources */, 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */, -- Gitee From 79b1467c5c6a9d65362f52353f9c21171acd53bc Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 29 Jan 2022 17:01:26 +0800 Subject: [PATCH 134/163] Pref // WindowNIB changes (localized). --- Source/Base.lproj/preferences.xib | 55 +++++++++++++++++++++++- Source/en.lproj/preferences.strings | 13 +++++- Source/ja.lproj/preferences.strings | 13 +++++- Source/zh-Hans.lproj/preferences.strings | 13 +++++- Source/zh-Hant.lproj/preferences.strings | 13 +++++- 5 files changed, 97 insertions(+), 10 deletions(-) diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index 8d6483fdf..38e9ef40b 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -459,21 +459,72 @@ - + - + + + + + + + + + + + + + diff --git a/Source/en.lproj/preferences.strings b/Source/en.lproj/preferences.strings index 54ad44cf6..403455750 100644 --- a/Source/en.lproj/preferences.strings +++ b/Source/en.lproj/preferences.strings @@ -125,6 +125,9 @@ /* Class = "NSTabViewItem"; label = "Dictionary"; ObjectID = "ISh-Da-hKv"; */ "ISh-Da-hKv.label" = "Dictionary"; +/* Class = "NSButtonCell"; title = "Sort entries when reloading user phrases and excluded phrases list"; ObjectID = "Li3-Yg-SOC"; */ +"Li3-Yg-SOC.title" = "Sort entries when reloading user phrases and excluded phrases list"; + /* Class = "NSTabViewItem"; label = "General"; ObjectID = "QUQ-oY-4Hc"; */ "QUQ-oY-4Hc.label" = "General"; @@ -155,11 +158,14 @@ /* Class = "NSButtonCell"; title = "Press ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ "f2j-xD-4xK.title" = "Press ESC key clears entire input buffer"; +/* Class = "NSButtonCell"; title = "Automatically reload user data files if changes detected"; ObjectID = "f8i-69-zxm"; */ +"f8i-69-zxm.title" = "Automatically reload user data files if changes detected"; + /* Class = "NSTextFieldCell"; title = "Change UI font size of candidate window for a better visual clarity."; ObjectID = "iRg-wx-Nx2"; */ "iRg-wx-Nx2.title" = "Change UI font size of candidate window for a better visual clarity."; -/* Class = "NSTextFieldCell"; title = "This dictionary page is under development."; ObjectID = "j48-5a-cEs"; */ -"j48-5a-cEs.title" = "This dictionary page is under development."; +/* Class = "NSTextFieldCell"; title = "Define your preferred action when user data files reload."; ObjectID = "j48-5a-cEs"; */ +"j48-5a-cEs.title" = "Define your preferred action when user data files reload."; /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; @@ -170,6 +176,9 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; +/* Class = "NSButtonCell"; title = "Sort entries when reloading the phrase replacement map"; ObjectID = "o60-vW-i1B"; */ +"o60-vW-i1B.title" = "Sort entries when reloading the phrase replacement map"; + /* Class = "NSMenuItem"; title = "Japanese"; ObjectID = "rVQ-Hx-cGi"; */ "rVQ-Hx-cGi.title" = "Japanese"; diff --git a/Source/ja.lproj/preferences.strings b/Source/ja.lproj/preferences.strings index ffedad7f3..f34ebbb31 100644 --- a/Source/ja.lproj/preferences.strings +++ b/Source/ja.lproj/preferences.strings @@ -125,6 +125,9 @@ /* Class = "NSTabViewItem"; label = "Dictionary"; ObjectID = "ISh-Da-hKv"; */ "ISh-Da-hKv.label" = "辞書"; +/* Class = "NSButtonCell"; title = "Sort entries when reloading user phrases and excluded phrases list"; ObjectID = "Li3-Yg-SOC"; */ +"Li3-Yg-SOC.title" = "ユーザー辞書と条目排除表を読み込むときに、内容の順番を整う"; + /* Class = "NSTabViewItem"; label = "General"; ObjectID = "QUQ-oY-4Hc"; */ "QUQ-oY-4Hc.label" = "全般"; @@ -155,11 +158,14 @@ /* Class = "NSButtonCell"; title = "Press ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ "f2j-xD-4xK.title" = "ESC キーで入力緩衝列を消す"; +/* Class = "NSButtonCell"; title = "Automatically reload user data files if changes detected"; ObjectID = "f8i-69-zxm"; */ +"f8i-69-zxm.title" = "変更されたユーザー辞書データを自動的に再読込"; + /* Class = "NSTextFieldCell"; title = "Change UI font size of candidate window for a better visual clarity."; ObjectID = "iRg-wx-Nx2"; */ "iRg-wx-Nx2.title" = "入力候補陣列の候補文字の字号をご指定ください。"; -/* Class = "NSTextFieldCell"; title = "This dictionary page is under development."; ObjectID = "j48-5a-cEs"; */ -"j48-5a-cEs.title" = "このページの機能はまだまだ施工中ですが……"; +/* Class = "NSTextFieldCell"; title = "Define your preferred action when user data files reload."; ObjectID = "j48-5a-cEs"; */ +"j48-5a-cEs.title" = "ユーザー辞書データの読み込む時のやることをご指示ください。"; /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; @@ -170,6 +176,9 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; +/* Class = "NSButtonCell"; title = "Sort entries when reloading the phrase replacement map"; ObjectID = "o60-vW-i1B"; */ +"o60-vW-i1B.title" = "言葉置換表を読み込むときに、内容の順番を整う"; + /* Class = "NSMenuItem"; title = "Japanese"; ObjectID = "rVQ-Hx-cGi"; */ "rVQ-Hx-cGi.title" = "和語"; diff --git a/Source/zh-Hans.lproj/preferences.strings b/Source/zh-Hans.lproj/preferences.strings index 37951c0d8..3ba951d51 100644 --- a/Source/zh-Hans.lproj/preferences.strings +++ b/Source/zh-Hans.lproj/preferences.strings @@ -125,6 +125,9 @@ /* Class = "NSTabViewItem"; label = "Dictionary"; ObjectID = "ISh-Da-hKv"; */ "ISh-Da-hKv.label" = "辞典"; +/* Class = "NSButtonCell"; title = "Sort entries when reloading user phrases and excluded phrases list"; ObjectID = "Li3-Yg-SOC"; */ +"Li3-Yg-SOC.title" = "在重新载入自订语汇与滤除语汇时,统整档案内容排序"; + /* Class = "NSTabViewItem"; label = "General"; ObjectID = "QUQ-oY-4Hc"; */ "QUQ-oY-4Hc.label" = "一般"; @@ -155,11 +158,14 @@ /* Class = "NSButtonCell"; title = "Press ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ "f2j-xD-4xK.title" = "敲 ESC 键以清空整个输入缓冲区"; +/* Class = "NSButtonCell"; title = "Automatically reload user data files if changes detected"; ObjectID = "f8i-69-zxm"; */ +"f8i-69-zxm.title" = "自动重新载入变更过的使用者数据内容"; + /* Class = "NSTextFieldCell"; title = "Change UI font size of candidate window for a better visual clarity."; ObjectID = "iRg-wx-Nx2"; */ "iRg-wx-Nx2.title" = "变更候选字窗的字型大小。"; -/* Class = "NSTextFieldCell"; title = "This dictionary page is under development."; ObjectID = "j48-5a-cEs"; */ -"j48-5a-cEs.title" = "该辞典页面相关功能正在施工。"; +/* Class = "NSTextFieldCell"; title = "Define your preferred action when user data files reload."; ObjectID = "j48-5a-cEs"; */ +"j48-5a-cEs.title" = "請指定在使用者數據重載時要啟用的功能。"; /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; @@ -170,6 +176,9 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; +/* Class = "NSButtonCell"; title = "Sort entries when reloading the phrase replacement map"; ObjectID = "o60-vW-i1B"; */ +"o60-vW-i1B.title" = "在重新载入语汇滤除表时,统整档案内容排序"; + /* Class = "NSMenuItem"; title = "Japanese"; ObjectID = "rVQ-Hx-cGi"; */ "rVQ-Hx-cGi.title" = "和语"; diff --git a/Source/zh-Hant.lproj/preferences.strings b/Source/zh-Hant.lproj/preferences.strings index a894f35b6..35d926a9c 100644 --- a/Source/zh-Hant.lproj/preferences.strings +++ b/Source/zh-Hant.lproj/preferences.strings @@ -125,6 +125,9 @@ /* Class = "NSTabViewItem"; label = "Dictionary"; ObjectID = "ISh-Da-hKv"; */ "ISh-Da-hKv.label" = "辭典"; +/* Class = "NSButtonCell"; title = "Sort entries when reloading user phrases and excluded phrases list"; ObjectID = "Li3-Yg-SOC"; */ +"Li3-Yg-SOC.title" = "在重新載入自訂語彙與濾除語彙時,統整檔案內容排序"; + /* Class = "NSTabViewItem"; label = "General"; ObjectID = "QUQ-oY-4Hc"; */ "QUQ-oY-4Hc.label" = "一般"; @@ -155,11 +158,14 @@ /* Class = "NSButtonCell"; title = "Press ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ "f2j-xD-4xK.title" = "敲 ESC 鍵以清空整個輸入緩衝區"; +/* Class = "NSButtonCell"; title = "Automatically reload user data files if changes detected"; ObjectID = "f8i-69-zxm"; */ +"f8i-69-zxm.title" = "自動重新載入變更過的使用者數據內容"; + /* Class = "NSTextFieldCell"; title = "Change UI font size of candidate window for a better visual clarity."; ObjectID = "iRg-wx-Nx2"; */ "iRg-wx-Nx2.title" = "變更候選字窗的字型大小。"; -/* Class = "NSTextFieldCell"; title = "This dictionary page is under development."; ObjectID = "j48-5a-cEs"; */ -"j48-5a-cEs.title" = "該辭典頁面相關功能正在施工。"; +/* Class = "NSTextFieldCell"; title = "Define your preferred action when user data files reload."; ObjectID = "j48-5a-cEs"; */ +"j48-5a-cEs.title" = "請指定在使用者數據重載時要啟用的功能。"; /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; @@ -170,6 +176,9 @@ /* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ "jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; +/* Class = "NSButtonCell"; title = "Sort entries when reloading the phrase replacement map"; ObjectID = "o60-vW-i1B"; */ +"o60-vW-i1B.title" = "在重新載入語彙濾除表時,統整檔案內容排序"; + /* Class = "NSMenuItem"; title = "Japanese"; ObjectID = "rVQ-Hx-cGi"; */ "rVQ-Hx-cGi.title" = "和語"; -- Gitee From 47031715b083498bc2467a03cca6410aff1f9981 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 29 Jan 2022 17:56:27 +0800 Subject: [PATCH 135/163] Pref // PrefModule Updates. --- Source/PreferencesModule.swift | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 593c27889..38959f453 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -15,6 +15,9 @@ private let kFunctionKeyKeyboardLayoutPreference = "FunctionKeyKeyboardLayout" private let kFunctionKeyKeyboardLayoutOverrideIncludeShift = "FunctionKeyKeyboardLayoutOverrideIncludeShift" private let kCandidateListTextSize = "CandidateListTextSize" private let kAppleLanguagesPreferences = "AppleLanguages" +private let kShouldAutoReloadUserDataFiles = "ShouldAutoReloadUserDataFiles" +private let kShouldAutoSortUserPhrasesAndExclListOnLoad = "ShouldAutoSortUserPhrasesAndExclListOnLoad" +private let kShouldAutoSortPhraseReplacementMapOnLoad = "ShouldAutoSortPhraseReplacementMapOnLoad" private let kSelectPhraseAfterCursorAsCandidatePreference = "SelectPhraseAfterCursorAsCandidate" private let kUseHorizontalCandidateListPreference = "UseHorizontalCandidateList" private let kComposingBufferSizePreference = "ComposingBufferSize" @@ -175,6 +178,9 @@ struct ComposingBufferSize { defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutOverrideIncludeShift) defaults.removeObject(forKey: kCandidateListTextSize) defaults.removeObject(forKey: kAppleLanguagesPreferences) + defaults.removeObject(forKey: kShouldAutoReloadUserDataFiles) + defaults.removeObject(forKey: kShouldAutoSortUserPhrasesAndExclListOnLoad) + defaults.removeObject(forKey: kShouldAutoSortPhraseReplacementMapOnLoad) defaults.removeObject(forKey: kSelectPhraseAfterCursorAsCandidatePreference) defaults.removeObject(forKey: kUseHorizontalCandidateListPreference) defaults.removeObject(forKey: kComposingBufferSizePreference) @@ -211,6 +217,21 @@ struct ComposingBufferSize { UserDefaults.standard.set(Preferences.chooseCandidateUsingSpace, forKey: kChooseCandidateUsingSpace) } + // 在檔案載入時,預設不啟用使用者自訂語彙表與語彙排除表的內容排序。 + if UserDefaults.standard.object(forKey: kShouldAutoReloadUserDataFiles) == nil { + UserDefaults.standard.set(Preferences.shouldAutoReloadUserDataFiles, forKey: kShouldAutoReloadUserDataFiles) + } + + // 在檔案載入時,預設不啟用語彙置換表的內容排序。 + if UserDefaults.standard.object(forKey: kShouldAutoSortUserPhrasesAndExclListOnLoad) == nil { + UserDefaults.standard.set(Preferences.ShouldAutoSortUserPhrasesAndExclListOnLoad, forKey: kShouldAutoSortUserPhrasesAndExclListOnLoad) + } + + // 自動檢測使用者自訂語彙數據的變動並載入。 + if UserDefaults.standard.object(forKey: kShouldAutoSortPhraseReplacementMapOnLoad) == nil { + UserDefaults.standard.set(Preferences.shouldAutoSortPhraseReplacementMapOnLoad, forKey: kShouldAutoSortPhraseReplacementMapOnLoad) + } + // 預設禁用 WinNT351 風格的注音選字模式(就是每個字都要選的那種),所以設成 false if UserDefaults.standard.object(forKey: kUseWinNT351BPMF) == nil { UserDefaults.standard.set(Preferences.useWinNT351BPMF, forKey: kUseWinNT351BPMF) @@ -270,6 +291,15 @@ struct ComposingBufferSize { @CandidateListTextSize(key: kCandidateListTextSize) @objc static var candidateListTextSize: CGFloat + + @UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: false) + @objc static var shouldAutoReloadUserDataFiles: Bool + + @UserDefault(key: kShouldAutoSortUserPhrasesAndExclListOnLoad, defaultValue: false) + @objc static var ShouldAutoSortUserPhrasesAndExclListOnLoad: Bool + + @UserDefault(key: kShouldAutoSortPhraseReplacementMapOnLoad, defaultValue: false) + @objc static var shouldAutoSortPhraseReplacementMapOnLoad: Bool @UserDefault(key: kSelectPhraseAfterCursorAsCandidatePreference, defaultValue: false) @objc static var selectPhraseAfterCursorAsCandidate: Bool -- Gitee From e68d02833d11522d14e1c913fb919321b8631894 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 29 Jan 2022 18:43:23 +0800 Subject: [PATCH 136/163] Pref // Bind Options to User Data LMs. --- Source/Engine/LanguageModel/PhraseReplacementMap.mm | 10 ++++++++-- Source/Engine/LanguageModel/UserPhrasesLM.mm | 8 +++++++- Source/InputMethodController.mm | 1 - 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Source/Engine/LanguageModel/PhraseReplacementMap.mm b/Source/Engine/LanguageModel/PhraseReplacementMap.mm index e2ed00ab9..433fcad5e 100644 --- a/Source/Engine/LanguageModel/PhraseReplacementMap.mm +++ b/Source/Engine/LanguageModel/PhraseReplacementMap.mm @@ -15,6 +15,7 @@ #include "KeyValueBlobReader.h" #include "PhraseReplacementMap.h" #include "LMConsolidator.h" +#include "vChewing-Swift.h" namespace vChewing { @@ -39,9 +40,14 @@ bool PhraseReplacementMap::open(const char *path) if (data) { return false; } - + LMConsolidator::FixEOF(path); - LMConsolidator::ConsolidateContent(path, false); + + if (Preferences.shouldAutoSortPhraseReplacementMapOnLoad) { + LMConsolidator::ConsolidateContent(path, true); + } else { + LMConsolidator::ConsolidateContent(path, false); + } fd = ::open(path, O_RDONLY); if (fd == -1) { diff --git a/Source/Engine/LanguageModel/UserPhrasesLM.mm b/Source/Engine/LanguageModel/UserPhrasesLM.mm index fe94a305b..f5901d40d 100644 --- a/Source/Engine/LanguageModel/UserPhrasesLM.mm +++ b/Source/Engine/LanguageModel/UserPhrasesLM.mm @@ -15,6 +15,7 @@ #include #include "LMConsolidator.h" #include "KeyValueBlobReader.h" +#include "vChewing-Swift.h" namespace vChewing { @@ -39,7 +40,12 @@ bool UserPhrasesLM::open(const char *path) } LMConsolidator::FixEOF(path); - LMConsolidator::ConsolidateContent(path, false); + + if (Preferences.ShouldAutoSortUserPhrasesAndExclListOnLoad) { + LMConsolidator::ConsolidateContent(path, true); + } else { + LMConsolidator::ConsolidateContent(path, false); + } fd = ::open(path, O_RDONLY); if (fd == -1) { diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 7f3303c56..2c7114952 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -7,7 +7,6 @@ */ #import "InputMethodController.h" -#include #import #import #import -- Gitee From 8ea5265bd1e6afc9901a1ebd2dabfc57942b067f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 29 Jan 2022 21:44:31 +0800 Subject: [PATCH 137/163] Pref // Bind Options to IMController. - At this moment we let the IME always automatically load the changes. Fingerprint-based conditioning will be implemented later. --- Source/InputMethodController.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 2c7114952..d9dbe91e4 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -190,6 +190,11 @@ static double FindHighestScore(const vector& nodes, double epsilon) NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; + // Load UserPhrases // 這裡今後需要改造成「驗證檔案指紋、根據驗證結果判定是否需要重新讀入」的形式。 + if (Preferences.shouldAutoReloadUserDataFiles) { + [self reloadUserPhrases:(id)nil]; + } + // reset the state _currentDeferredClient = nil; _currentCandidateClient = nil; -- Gitee From 9a897c84539453ba9f63058409d007ea1e85a5b9 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 29 Jan 2022 22:25:36 +0800 Subject: [PATCH 138/163] Pref // Bind Options to Menu Option Visibility. - The "Reload User Phrases" menu command will not show unless either the Alt key gets pressed or the autoreload of user phrases data is checked Enabled in the Preferences window. --- Source/InputMethodController.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index d9dbe91e4..429374ca8 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -163,8 +163,10 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItemWithTitle:NSLocalizedString(@"Edit Phrase Replacement Table", @"") action:@selector(openPhraseReplacement:) keyEquivalent:@""]; } - [menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; - + if (optionKeyPressed || !Preferences.shouldAutoReloadUserDataFiles) { + [menu addItemWithTitle:NSLocalizedString(@"Reload User Phrases", @"") action:@selector(reloadUserPhrases:) keyEquivalent:@""]; + } + [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ [menu addItemWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; -- Gitee From 115f197b72b8e872a1a0a75e5daffbd67eb950e1 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 29 Jan 2022 22:31:24 +0800 Subject: [PATCH 139/163] Pref // Other localization tweaks. --- Source/Base.lproj/preferences.xib | 4 ++-- Source/InputMethodController.mm | 2 +- Source/en.lproj/Localizable.strings | 6 +++--- Source/ja.lproj/Localizable.strings | 6 +++--- Source/zh-Hans.lproj/Localizable.strings | 6 +++--- Source/zh-Hant.lproj/Localizable.strings | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index 38e9ef40b..5693b12a0 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -85,8 +85,8 @@ - - + + diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 429374ca8..8677f5480 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -157,7 +157,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) [menu addItem:[NSMenuItem separatorItem]]; // ------------------------------ - [menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; + [menu addItemWithTitle:NSLocalizedString(@"Edit User Phrases…", @"") action:@selector(openUserPhrases:) keyEquivalent:@""]; if (optionKeyPressed) { [menu addItemWithTitle:NSLocalizedString(@"Edit Excluded Phrases", @"") action:@selector(openExcludedPhrases:) keyEquivalent:@""]; [menu addItemWithTitle:NSLocalizedString(@"Edit Phrase Replacement Table", @"") action:@selector(openPhraseReplacement:) keyEquivalent:@""]; diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 016d461c6..0732f0daa 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -16,16 +16,16 @@ "Force KangXi Writing" = "Force KangXi Writing"; "NotificationSwitchON" = "✔ ON"; "NotificationSwitchOFF" = "✘ OFF"; -"Edit User Phrases" = "Edit User Phrases"; +"Edit User Phrases…" = "Edit User Phrases…"; "Reload User Phrases" = "Reload User Phrases"; "Unable to create the user phrase file." = "Unable to create the user phrase file."; "Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\"."; -"Edit Excluded Phrases" = "Edit Excluded Phrases"; +"Edit Excluded Phrases" = "Edit Excluded Phrases…"; "Use Half-Width Punctuations" = "Use Half-Width Punctuations"; "\"%@\" length must ≥ 2 for a user phrase." = "\"%@\" length must ≥ 2 for a user phrase."; "\"%@\" length too long for a user phrase." = "\"%@\" length too long for a user phrase."; "\"%@\" selected. ENTER to add user phrase." = "\"%@\" selected. ENTER to add user phrase."; -"Edit Phrase Replacement Table" = "Edit Phrase Replacement Table"; +"Edit Phrase Replacement Table" = "Edit Phrase Replacement Table…"; "Use Phrase Replacement" = "Use Phrase Replacement"; "Candidates keys cannot be empty." = "Candidates keys cannot be empty."; "Candidate keys can only contain ASCII characters like alphanumerals." = "Candidate keys can only contain ASCII characters like alphanumerals."; diff --git a/Source/ja.lproj/Localizable.strings b/Source/ja.lproj/Localizable.strings index 1c34d83c8..a7a60c75d 100644 --- a/Source/ja.lproj/Localizable.strings +++ b/Source/ja.lproj/Localizable.strings @@ -16,16 +16,16 @@ "Force KangXi Writing" = "康熙文字変換モード"; "NotificationSwitchON" = "✔ 機能起動"; "NotificationSwitchOFF" = "✘ 機能停止"; -"Edit User Phrases" = "ユーザー辞書を編集"; +"Edit User Phrases…" = "ユーザー辞書を編集…"; "Reload User Phrases" = "ユーザー辞書を再び読込む"; "Unable to create the user phrase file." = "ユーザー辞書ファイルの作成は失敗しました。"; "Please check the permission of at \"%@\"." = "「%@」に書き出す権限は不足らしい。"; -"Edit Excluded Phrases" = "辞書条目排除表を編集"; +"Edit Excluded Phrases" = "辞書条目排除表を編集…"; "Use Half-Width Punctuations" = "半角句読機能を起用"; "\"%@\" length must ≥ 2 for a user phrase." = "「%@」もう1つ文字のお選びを。"; "\"%@\" length too long for a user phrase." = "「%@」文字数過剰で登録不可。"; "\"%@\" selected. ENTER to add user phrase." = "「%@」を ENTER で辞書に登録。"; -"Edit Phrase Replacement Table" = "言葉置換表を編集"; +"Edit Phrase Replacement Table" = "言葉置換表を編集…"; "Use Phrase Replacement" = "言葉置換機能"; "Candidates keys cannot be empty." = "言選り用キー陣列に何かキーをご登録ください。"; "Candidate keys can only contain ASCII characters like alphanumerals." = "言選り用キー陣列にはASCII文字だけをご登録ください(英数など)。"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index a67e8e6d2..e9926ac7f 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -16,16 +16,16 @@ "Force KangXi Writing" = "康熙繁体字模式"; "NotificationSwitchON" = "✔ 已启用"; "NotificationSwitchOFF" = "✘ 已停用"; -"Edit User Phrases" = "编辑自订语汇"; +"Edit User Phrases…" = "编辑自订语汇…"; "Reload User Phrases" = "重载自订语汇"; "Unable to create the user phrase file." = "无法创建自订语汇档案。"; "Please check the permission of at \"%@\"." = "请检查此处的存取权限:\"%@\"."; -"Edit Excluded Phrases" = "编辑要滤除的语汇"; +"Edit Excluded Phrases" = "编辑要滤除的语汇…"; "Use Half-Width Punctuations" = "啟用半角標點輸出"; "\"%@\" length must ≥ 2 for a user phrase." = "「%@」字数不足以自订语汇。"; "\"%@\" length too long for a user phrase." = "「%@」字数太长、无法自订。"; "\"%@\" selected. ENTER to add user phrase." = "「%@」敲 Enter 添入自订语汇。"; -"Edit Phrase Replacement Table" = "编辑语汇置换表"; +"Edit Phrase Replacement Table" = "编辑语汇置换表…"; "Use Phrase Replacement" = "使用语汇置换"; "Candidates keys cannot be empty." = "您必须指定选字键。"; "Candidate keys can only contain ASCII characters like alphanumerals." = "选字键只能是英数等 ASCII 字符。"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 79fa9be8d..aac48d3f3 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -16,16 +16,16 @@ "Force KangXi Writing" = "康熙繁體字模式"; "NotificationSwitchON" = "✔ 已啟用"; "NotificationSwitchOFF" = "✘ 已停用"; -"Edit User Phrases" = "編輯自訂語彙"; +"Edit User Phrases…" = "編輯自訂語彙…"; "Reload User Phrases" = "重載自訂語彙"; "Unable to create the user phrase file." = "無法創建自訂語彙檔案。"; "Please check the permission of at \"%@\"." = "請檢查此處的存取權限:\"%@\"."; -"Edit Excluded Phrases" = "編輯要濾除的語彙"; +"Edit Excluded Phrases" = "編輯要濾除的語彙…"; "Use Half-Width Punctuations" = "啟用半形標點輸出"; "\"%@\" length must ≥ 2 for a user phrase." = "「%@」字數不足以自訂語彙。"; "\"%@\" length too long for a user phrase." = "「%@」字數太長、無法自訂。"; "\"%@\" selected. ENTER to add user phrase." = "「%@」敲 Enter 添入自訂語彙。"; -"Edit Phrase Replacement Table" = "編輯語彙置換表"; +"Edit Phrase Replacement Table" = "編輯語彙置換表…"; "Use Phrase Replacement" = "使用語彙置換"; "Candidates keys cannot be empty." = "您必須指定選字鍵。"; "Candidate keys can only contain ASCII characters like alphanumerals." = "選字鍵只能是英數等 ASCII 字符。"; -- Gitee From 541e96ab704be5c6f25f59a41d411dcfc01aa6ac Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 31 Jan 2022 12:29:34 +0800 Subject: [PATCH 140/163] Zonble: Use fsStreamHelper to monitor data changes. --- Source/AppDelegate.swift | 14 +++- .../LanguageModel/FSEventStreamHelper.swift | 80 +++++++++++++++++++ Source/InputMethodController.mm | 5 -- Source/LanguageModelManager.mm | 2 +- Source/vChewing-Bridging-Header.h | 1 + vChewing.xcodeproj/project.pbxproj | 4 + 6 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 Source/Engine/LanguageModel/FSEventStreamHelper.swift diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 0959b114f..65552c772 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -128,13 +128,23 @@ struct VersionUpdateApi { } @objc(AppDelegate) -class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate { +class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate, FSEventStreamHelperDelegate { + func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) { + DispatchQueue.main.async { + if Preferences.shouldAutoReloadUserDataFiles { + LanguageModelManager.loadUserPhrases() + LanguageModelManager.loadUserPhraseReplacement() + } + } + } + @IBOutlet weak var window: NSWindow? private var preferencesWindowController: PreferencesWindowController? private var aboutWindowController: frmAboutWindow? // New About Window private var checkTask: URLSessionTask? private var updateNextStepURL: URL? + private var fsStreamHelper = FSEventStreamHelper(path: LanguageModelManager.dataFolderPath, queue: DispatchQueue(label: "User Phrases")) // 補上 dealloc deinit { @@ -149,6 +159,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle LanguageModelManager.loadCNSData() LanguageModelManager.loadUserPhrases() LanguageModelManager.loadUserPhraseReplacement() + fsStreamHelper.delegate = self + _ = fsStreamHelper.start() Preferences.setMissingDefaults() diff --git a/Source/Engine/LanguageModel/FSEventStreamHelper.swift b/Source/Engine/LanguageModel/FSEventStreamHelper.swift new file mode 100644 index 000000000..8db85e3bc --- /dev/null +++ b/Source/Engine/LanguageModel/FSEventStreamHelper.swift @@ -0,0 +1,80 @@ +/* + * FSEventStreamHelper.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +import Cocoa + +public protocol FSEventStreamHelperDelegate: AnyObject { + func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) +} + +public class FSEventStreamHelper : NSObject { + + public struct Event { + var path: String + var flags: FSEventStreamEventFlags + var id: FSEventStreamEventId + } + + public let path: String + public let dispatchQueue: DispatchQueue + public weak var delegate: FSEventStreamHelperDelegate? + + @objc public init(path: String, queue: DispatchQueue) { + self.path = path + self.dispatchQueue = queue + } + + private var stream: FSEventStreamRef? = nil + + public func start() -> Bool { + if stream != nil { + return false + } + var context = FSEventStreamContext() + context.info = Unmanaged.passUnretained(self).toOpaque() + guard let stream = FSEventStreamCreate(nil, { + (stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in + let helper = Unmanaged.fromOpaque(clientCallBackInfo!).takeUnretainedValue() + let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer.self) + let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount) + let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount) + let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount) + let events = (0..& nodes, double epsilon) NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - // Load UserPhrases // 這裡今後需要改造成「驗證檔案指紋、根據驗證結果判定是否需要重新讀入」的形式。 - if (Preferences.shouldAutoReloadUserDataFiles) { - [self reloadUserPhrases:(id)nil]; - } - // reset the state _currentDeferredClient = nil; _currentCandidateClient = nil; diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 7ab0ff8aa..c70b20d94 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -181,7 +181,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing [writeFile writeData:data]; [writeFile closeFile]; - [self loadUserPhrases]; + // [self loadUserPhrases]; // Not Needed since AppDelegate is handling this. return YES; } diff --git a/Source/vChewing-Bridging-Header.h b/Source/vChewing-Bridging-Header.h index 2d5893b2a..19b78ffda 100644 --- a/Source/vChewing-Bridging-Header.h +++ b/Source/vChewing-Bridging-Header.h @@ -17,4 +17,5 @@ + (void)loadCNSData; + (void)loadUserPhrases; + (void)loadUserPhraseReplacement; +@property (class, readonly, nonatomic) NSString *dataFolderPath; @end diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 79091b13c..abe9cde9f 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 5B5F4F972792A4EA00922DC2 /* UserPhrasesLM.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F4F962792A4EA00922DC2 /* UserPhrasesLM.mm */; }; 5B6797B52794822C004AC7CE /* PhraseReplacementMap.h in Sources */ = {isa = PBXBuildFile; fileRef = 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */; }; 5B810D9F27A3A5E50032C1A9 /* LMConsolidator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B810D9D27A3A5E50032C1A9 /* LMConsolidator.mm */; }; + 5B9DD0A927A7950D00ED335A /* FSEventStreamHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9DD0A827A7950D00ED335A /* FSEventStreamHelper.swift */; }; 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; }; 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */; }; @@ -124,6 +125,7 @@ 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 5B9DD0A827A7950D00ED335A /* FSEventStreamHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSEventStreamHelper.swift; sourceTree = ""; }; 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewingInstaller-Bridging-Header.h"; sourceTree = ""; }; 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyValueBlobReader.h; sourceTree = ""; }; 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeyValueBlobReader.cpp; sourceTree = ""; }; @@ -264,6 +266,7 @@ 5B6797B32794822C004AC7CE /* PhraseReplacementMap.h */, 5B810D9D27A3A5E50032C1A9 /* LMConsolidator.mm */, 5B810D9E27A3A5E50032C1A9 /* LMConsolidator.h */, + 5B9DD0A827A7950D00ED335A /* FSEventStreamHelper.swift */, ); path = LanguageModel; sourceTree = ""; @@ -714,6 +717,7 @@ 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */, 5BDD25F9279D6D1200AA18F8 /* ioapi.m in Sources */, 5BE798A72793280C00337FF9 /* NotifierController.swift in Sources */, + 5B9DD0A927A7950D00ED335A /* FSEventStreamHelper.swift in Sources */, 5B5F4F93279294A300922DC2 /* LanguageModelManager.mm in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.swift in Sources */, -- Gitee From 0b996632ca0d0f362800ac94098b5832af84b531 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 31 Jan 2022 12:42:52 +0800 Subject: [PATCH 141/163] Pref // Auto reload user phrases changes by default. --- Source/PreferencesModule.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 38959f453..48d7d3a78 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -292,7 +292,7 @@ struct ComposingBufferSize { @CandidateListTextSize(key: kCandidateListTextSize) @objc static var candidateListTextSize: CGFloat - @UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: false) + @UserDefault(key: kShouldAutoReloadUserDataFiles, defaultValue: true) @objc static var shouldAutoReloadUserDataFiles: Bool @UserDefault(key: kShouldAutoSortUserPhrasesAndExclListOnLoad, defaultValue: false) -- Gitee From e7dcbe7e91880bb66b47c9af6313e16b86a659e9 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 30 Jan 2022 00:20:32 +0800 Subject: [PATCH 142/163] Zonble: IMC // Refactoring IMController. - This commit already has vChewing Feature Customizations Applied. --- .../Engine/ControllerModules/InputState.swift | 219 +++ Source/Engine/ControllerModules/KeyHandler.h | 49 + Source/Engine/ControllerModules/KeyHandler.mm | 1179 +++++++++++ .../ControllerModules/KeyHandlerInput.swift | 178 ++ .../KeyValueBlobReader.cpp | 0 .../KeyValueBlobReader.h | 0 .../Engine/Gramambular/BlockReadingBuilder.h | 31 +- Source/Engine/Keyboard/EmacsKeyHelper.swift | 28 - Source/InputMethodController.h | 39 +- Source/InputMethodController.mm | 1749 ++++------------- Source/LanguageModelManager.mm | 24 +- Source/en.lproj/Localizable.strings | 4 +- Source/ja.lproj/Localizable.strings | 4 +- Source/zh-Hans.lproj/Localizable.strings | 4 +- Source/zh-Hant.lproj/Localizable.strings | 4 +- vChewing.xcodeproj/project.pbxproj | 32 +- 16 files changed, 2070 insertions(+), 1474 deletions(-) create mode 100644 Source/Engine/ControllerModules/InputState.swift create mode 100644 Source/Engine/ControllerModules/KeyHandler.h create mode 100644 Source/Engine/ControllerModules/KeyHandler.mm create mode 100644 Source/Engine/ControllerModules/KeyHandlerInput.swift rename Source/Engine/{vChewing => ControllerModules}/KeyValueBlobReader.cpp (100%) rename Source/Engine/{vChewing => ControllerModules}/KeyValueBlobReader.h (100%) delete mode 100644 Source/Engine/Keyboard/EmacsKeyHelper.swift diff --git a/Source/Engine/ControllerModules/InputState.swift b/Source/Engine/ControllerModules/InputState.swift new file mode 100644 index 000000000..81d0af544 --- /dev/null +++ b/Source/Engine/ControllerModules/InputState.swift @@ -0,0 +1,219 @@ +/* + * InputState.cpp + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +import Cocoa + +/// Represents the states for the input method controller. +/// +/// An input method is actually a finite state machine. It receives the inputs +/// from hardware like keyboard and mouse, changes its state, updates user +/// interface by the state, and finally produces the text output and then them +/// to the client apps. It should be a one-way data flow, and the user interface +/// and text output should follow unconditionally one single data source. +/// +/// The InputState class is for representing what the input controller is doing, +/// and the place to store the variables that could be used. For example, the +/// array for the candidate list is useful only when the user is choosing a +/// candidate, and the array should not exist when the input controller is in +/// another state. +/// +/// They are immutable objects. When the state changes, the controller should +/// create a new state object to replace the current state instead of modifying +/// the existing one. +/// +/// vChewing's input controller has following possible states: +/// +/// - Deactivated: The user is not using vChewing yet. +/// - Empty: The user has switched to vChewing but did not input anything yet, +/// or, he or she has committed text into the client apps and starts a new +/// input phase. +/// - Committing: The input controller is sending text to the client apps. +/// - Inputting: The user has inputted something and the input buffer is +/// visible. +/// - Marking: The user is creating a area in the input buffer and about to +/// create a new user phrase. +/// - Choosing Candidate: The candidate window is open to let the user to choose +/// one among the candidates. +class InputState: NSObject { +} + +/// Represents that the input controller is deactivated. +class InputStateDeactivated: InputState { + override var description: String { + "" + } +} + +/// Represents that the composing buffer is empty. +class InputStateEmpty: InputState { + @objc var composingBuffer: String { + "" + } +} + +/// Represents that the composing buffer is empty. +class InputStateEmptyIgnoringPreviousState: InputState { + @objc var composingBuffer: String { + "" + } +} + +/// Represents that the input controller is committing text into client app. +class InputStateCommitting: InputState { + @objc private(set) var poppedText: String = "" + + @objc convenience init(poppedText: String) { + self.init() + self.poppedText = poppedText + } + + override var description: String { + "" + } +} + +/// Represents that the composing buffer is not empty. +class InputStateNotEmpty: InputState { + @objc private(set) var composingBuffer: String = "" + @objc private(set) var cursorIndex: UInt = 0 + + @objc init(composingBuffer: String, cursorIndex: UInt) { + self.composingBuffer = composingBuffer + self.cursorIndex = cursorIndex + } + + override var description: String { + "" + } +} + +/// Represents that the user is inputting text. +class InputStateInputting: InputStateNotEmpty { + @objc var bpmfReading: String = "" + @objc var bpmfReadingCursorIndex: UInt8 = 0 + @objc var poppedText: String = "" + + @objc override init(composingBuffer: String, cursorIndex: UInt) { + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + } + + @objc var attributedString: NSAttributedString { + let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0 + ]) + return attributedSting + } + + override var description: String { + "" + } +} + +private let kMinMarkRangeLength = 2 +private let kMaxMarkRangeLength = Preferences.maxCandidateLength + +/// Represents that the user is marking a range in the composing buffer. +class InputStateMarking: InputStateNotEmpty { + @objc private(set) var markerIndex: UInt + @objc private(set) var markedRange: NSRange + @objc var tooltip: String { + + if Preferences.phraseReplacementEnabled { + return NSLocalizedString("⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "") + } + + if markedRange.length == 0 { + return "" + } + + let text = (composingBuffer as NSString).substring(with: markedRange) + if markedRange.length < kMinMarkRangeLength { + return String(format: NSLocalizedString("\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text) + } else if (markedRange.length > kMaxMarkRangeLength) { + return String(format: NSLocalizedString("\"%@\" length should ≤ %d for a user phrase.", comment: ""), text, kMaxMarkRangeLength) + } + return String(format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: ""), text) + } + + @objc private(set) var readings: [String] = [] + + @objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) { + self.markerIndex = markerIndex + let begin = min(cursorIndex, markerIndex) + let end = max(cursorIndex, markerIndex) + markedRange = NSMakeRange(Int(begin), Int(end - begin)) + self.readings = readings + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + } + + @objc var attributedString: NSAttributedString { + let attributedSting = NSMutableAttributedString(string: composingBuffer) + let end = markedRange.location + markedRange.length + + attributedSting.setAttributes([ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0 + ], range: NSRange(location: 0, length: markedRange.location)) + attributedSting.setAttributes([ + .underlineStyle: NSUnderlineStyle.thick.rawValue, + .markedClauseSegment: 1 + ], range: markedRange) + attributedSting.setAttributes([ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 2 + ], range: NSRange(location: end, + length: composingBuffer.count - end)) + return attributedSting + } + + override var description: String { + "" + } + + @objc func convertToInputting() -> InputStateInputting { + let state = InputStateInputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) + return state + } + + @objc var validToWrite: Bool { + markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength + } + + @objc var userPhrase: String { + let text = (composingBuffer as NSString).substring(with: markedRange) + let end = markedRange.location + markedRange.length + let readings = readings[markedRange.location.." + } +} diff --git a/Source/Engine/ControllerModules/KeyHandler.h b/Source/Engine/ControllerModules/KeyHandler.h new file mode 100644 index 000000000..f4be044e0 --- /dev/null +++ b/Source/Engine/ControllerModules/KeyHandler.h @@ -0,0 +1,49 @@ +/* + * KeyHandler.h + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +#import +#import +#import "vChewing-Swift.h" + +@class KeyHandlerInput; +@class InputState; +@class InputStateInputting; +@class InputStateMarking; + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *const kBopomofoModeIdentifierCHS; +extern NSString *const kBopomofoModeIdentifierCHT; + +@class KeyHandler; + +@protocol KeyHandlerDelegate +- (VTCandidateController *)candidateControllerForKeyHandler:(KeyHandler *)keyHandler; +- (void)keyHandler:(KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(VTCandidateController *)controller; +- (BOOL)keyHandler:(KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(InputStateMarking *)state; +@end + +@interface KeyHandler : NSObject + +- (BOOL)handleInput:(KeyHandlerInput *)input + state:(InputState *)state + stateCallback:(void (^)(InputState *))stateCallback +candidateSelectionCallback:(void (^)(void))candidateSelectionCallback + errorCallback:(void (^)(void))errorCallback; + +- (void)syncWithPreferences; +- (void)fixNodeWithValue:(std::string)value; +- (void)clear; + +- (InputStateInputting *)_buildInputtingState; + +@property (strong, nonatomic) NSString *inputMode; +@property (weak, nonatomic) id delegate; +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Engine/ControllerModules/KeyHandler.mm b/Source/Engine/ControllerModules/KeyHandler.mm new file mode 100644 index 000000000..195afee6c --- /dev/null +++ b/Source/Engine/ControllerModules/KeyHandler.mm @@ -0,0 +1,1179 @@ +/* + * KeyHandler.mm + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +#import "Mandarin.hh" +#import "Gramambular.h" +#import "vChewingLM.h" +#import "UserOverrideModel.h" +#import "LanguageModelManager.h" +#import "OVUTF8Helper.h" +#import "KeyHandler.h" +#import "vChewing-Swift.h" + +using namespace std; +using namespace Taiyan::Mandarin; +using namespace Taiyan::Gramambular; +using namespace vChewing; +using namespace OpenVanilla; + +NSString *const kBopomofoModeIdentifierCHT = @"org.atelierInmu.inputmethod.vChewing.TradBopomofo"; +NSString *const kBopomofoModeIdentifierCHS = @"org.atelierInmu.inputmethod.vChewing.SimpBopomofo"; + +static const double kEpsilon = 0.000001; + +static double FindHighestScore(const vector &nodes, double epsilon) { + double highestScore = 0.0; + for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { + double score = ni->node->highestUnigramScore(); + if (score > highestScore) { + highestScore = score; + } + } + return highestScore + epsilon; +} + +// sort helper +class NodeAnchorDescendingSorter +{ +public: + bool operator()(const NodeAnchor &a, const NodeAnchor &b) const { + return a.node->key().length() > b.node->key().length(); + } +}; + +// if DEBUG is defined, a DOT file (GraphViz format) will be written to the +// specified path every time the grid is walked +#if DEBUG +static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; +#endif + + +@implementation KeyHandler +{ + // the reading buffer that takes user input + Taiyan::Mandarin::BopomofoReadingBuffer *_bpmfReadingBuffer; + + // language model + vChewing::vChewingLM *_languageModel; + + // user override model + vChewing::UserOverrideModel *_userOverrideModel; + + // the grid (lattice) builder for the unigrams (and bigrams) + Taiyan::Gramambular::BlockReadingBuilder *_builder; + + // latest walked path (trellis) using the Viterbi algorithm + std::vector _walkedNodes; + + NSString *_inputMode; +} + +//@synthesize inputMode = _inputMode; +@synthesize delegate = _delegate; + +- (NSString *)inputMode +{ + return _inputMode; +} + +- (void)setInputMode:(NSString *)value +{ + NSString *newInputMode; + vChewingLM *newLanguageModel; + UserOverrideModel *newUserOverrideModel; + + if ([value isKindOfClass:[NSString class]] && [value isEqual:kBopomofoModeIdentifierCHS]) { + newInputMode = kBopomofoModeIdentifierCHS; + newLanguageModel = [LanguageModelManager languageModelCoreCHS]; + newUserOverrideModel = [LanguageModelManager userOverrideModelCHS]; + } else { + newInputMode = kBopomofoModeIdentifierCHT; + newLanguageModel = [LanguageModelManager languageModelCoreCHT]; + newUserOverrideModel = [LanguageModelManager userOverrideModelCHT]; + } + + // 自 Preferences 模組讀入自訂語彙置換功能開關狀態。 + newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); + + // 自 Preferences 模組讀取全字庫模式開關狀態。 + newLanguageModel->setCNSEnabled(Preferences.cns11643Enabled); + + // Only apply the changes if the value is changed + if (![_inputMode isEqualToString:newInputMode]) { + [[NSUserDefaults standardUserDefaults] synchronize]; + + _inputMode = newInputMode; + _languageModel = newLanguageModel; + _userOverrideModel = newUserOverrideModel; + + if (_builder) { + delete _builder; + _builder = new BlockReadingBuilder(_languageModel); + _builder->setJoinSeparator("-"); + } + + if (!_bpmfReadingBuffer->isEmpty()) { + _bpmfReadingBuffer->clear(); + } + } +} + +- (void)dealloc +{ + // clean up everything + if (_bpmfReadingBuffer) { + delete _bpmfReadingBuffer; + } + + if (_builder) { + delete _builder; + } +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout()); + + // create the lattice builder + _languageModel = [LanguageModelManager languageModelCoreCHT]; + _languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); + _languageModel->setCNSEnabled(Preferences.cns11643Enabled); + _userOverrideModel = [LanguageModelManager userOverrideModelCHT]; + + _builder = new BlockReadingBuilder(_languageModel); + + // each Mandarin syllable is separated by a hyphen + _builder->setJoinSeparator("-"); + _inputMode = kBopomofoModeIdentifierCHT; + } + return self; +} + +- (void)syncWithPreferences +{ + NSInteger layout = Preferences.keyboardLayout; + switch (layout) { + case KeyboardLayoutStandard: + _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::StandardLayout()); + break; + case KeyboardLayoutEten: + _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::ETenLayout()); + break; + case KeyboardLayoutHsu: + _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::HsuLayout()); + break; + case KeyboardLayoutEten26: + _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::ETen26Layout()); + break; + case KeyboardLayoutHanyuPinyin: + _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::HanyuPinyinLayout()); + break; + case KeyboardLayoutIBM: + _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::IBMLayout()); + break; + default: + _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::StandardLayout()); + Preferences.keyboardLayout = KeyboardLayoutStandard; + } +} + +- (void)fixNodeWithValue:(std::string)value +{ + size_t cursorIndex = [self _actualCandidateCursorIndex]; + _builder->grid().fixNodeSelectedCandidate(cursorIndex, value); + if (Preferences.useWinNT351BPMF) { + _userOverrideModel->observe(_walkedNodes, cursorIndex, value, [[NSDate date] timeIntervalSince1970]); + } + [self _walk]; +} + +- (void)clear +{ + _bpmfReadingBuffer->clear(); + _builder->clear(); + _walkedNodes.clear(); +} + +- (string)_currentLayout +{ + NSString *keyboardLayoutName = Preferences.keyboardLayoutName; + string layout = string(keyboardLayoutName.UTF8String) + string("_"); + return layout; +} + +- (BOOL)handleInput:(KeyHandlerInput *)input state:(InputState *)inState stateCallback:(void (^)(InputState *))stateCallback candidateSelectionCallback:(void (^)(void))candidateSelectionCallback errorCallback:(void (^)(void))errorCallback +{ + InputState *state = inState; + UniChar charCode = input.charCode; + vChewingEmacsKey emacsKey = input.emacsKey; + + // if the inputText is empty, it's a function key combination, we ignore it + if (![input.inputText length]) { + return NO; + } + + // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it + BOOL isFunctionKey = ([input isCommandHold] || [input isControlHold] || [input isOptionHold] || [input isNumericPad]); + if (![state isKindOfClass:[InputStateNotEmpty class]] && isFunctionKey) { + return NO; + } + + // Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo. + if (charCode == 8 || charCode == 13 || [input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey] || [input isCursorForward] || [input isCursorBackward]) { + // do nothing if backspace is pressed -- we ignore the key + } else if ([input isCapsLockOn]) { + // process all possible combination, we hope. + [self clear]; + InputStateEmpty *emptyState = [[InputStateEmpty alloc] init]; + stateCallback(emptyState); + + // first commit everything in the buffer. + if ([input isShiftHold]) { + return NO; + } + + // if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char insertions. + if (charCode < 0x80 && !isprint(charCode)) { + return NO; + } + + // when shift is pressed, don't do further processing, since it outputs capital letter anyway. + InputStateCommitting *committingState = [[InputStateCommitting alloc] initWithPoppedText:[input.inputText lowercaseString]]; + stateCallback(committingState); + stateCallback(emptyState); + return YES; + } + + if ([input isNumericPad]) { + if (![input isLeft] && ![input isRight] && ![input isDown] && ![input isUp] && charCode != 32 && isprint(charCode)) { + [self clear]; + InputStateEmpty *emptyState = [[InputStateEmpty alloc] init]; + stateCallback(emptyState); + InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:[input.inputText lowercaseString]]; + stateCallback(committing); + stateCallback(emptyState); + return YES; + } + } + + // MARK: Handle Candidates + if ([state isKindOfClass:[InputStateChoosingCandidate class]]) { + return [self _handleCandidateState:(InputStateChoosingCandidate *) state input:input stateCallback:stateCallback candidateSelectionCallback:candidateSelectionCallback errorCallback:errorCallback]; + } + + // MARK: Handle Marking + if ([state isKindOfClass:[InputStateMarking class]]) { + InputStateMarking *marking = (InputStateMarking *) state; + if ([self _handleMarkingState:(InputStateMarking *) state input:input stateCallback:stateCallback candidateSelectionCallback:candidateSelectionCallback errorCallback:errorCallback]) { + return YES; + } + state = [marking convertToInputting]; + stateCallback(state); + } + + bool composeReading = false; + + // MARK: Handle BPMF Keys + // see if it's valid BPMF reading + if (_bpmfReadingBuffer->isValidKey((char) charCode)) { + _bpmfReadingBuffer->combineKey((char) charCode); + + // if we have a tone marker, we have to insert the reading to the + // builder in other words, if we don't have a tone marker, we just + // update the composing buffer + composeReading = _bpmfReadingBuffer->hasToneMarker(); + if (!composeReading) { + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + return YES; + } + } + + // see if we have composition if Enter/Space is hit and buffer is not empty + // this is bit-OR'ed so that the tone marker key is also taken into account + composeReading |= (!_bpmfReadingBuffer->isEmpty() && (charCode == 32 || charCode == 13)); + if (composeReading) { + // combine the reading + string reading = _bpmfReadingBuffer->syllable().composedString(); + + // see if we have a unigram for this + if (!_languageModel->hasUnigramsForKey(reading)) { + errorCallback(); + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + return YES; + } + + // and insert it into the lattice + _builder->insertReadingAtCursor(reading); + + // then walk the lattice + NSString *poppedText = [self _popOverflowComposingTextAndWalk]; + + // get user override model suggestion + string overrideValue = (Preferences.useWinNT351BPMF) ? "" : + _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + + if (!overrideValue.empty()) { + size_t cursorIndex = [self _actualCandidateCursorIndex]; + vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); + double highestScore = FindHighestScore(nodes, kEpsilon); + _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, highestScore); + } + + // then update the text + _bpmfReadingBuffer->clear(); + + InputStateInputting *inputting = [self _buildInputtingState]; + inputting.poppedText = poppedText; + stateCallback(inputting); + + // 模擬 WINNT 351 ㄅ半注音,就是每個漢字都自動要選字的那種注音。 + // 嚴格來講不能算純正的ㄅ半注音,畢竟候選字的順序不可能會像當年那樣了。 + // 如果簡體中文用戶不知道ㄅ半注音是什麼的話,拿全拼輸入法來比喻恐怕比較恰當。 + if (Preferences.useWinNT351BPMF) { + InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode]; + if (choosingCandidates.candidates.count == 1) { + [self clear]; + InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:choosingCandidates.candidates.firstObject]; + stateCallback(committing); + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + stateCallback(empty); + } else { + stateCallback(choosingCandidates); + } + } + + // and tells the client that the key is consumed + return YES; + } + + // MARK: Space and Down + // keyCode 125 = Down, charCode 32 = Space + if (_bpmfReadingBuffer->isEmpty() && + [state isKindOfClass:[InputStateNotEmpty class]] && + ([input isExtraChooseCandidateKey] || charCode == 32 || (input.useVerticalMode && ([input isVerticalModeOnlyChooseCandidateKey])))) { + if (charCode == 32) { + // if the spacebar is NOT set to be a selection key + if ([input isShiftHold] || !Preferences.chooseCandidateUsingSpace) { + if (_builder->cursorIndex() >= _builder->length()) { + [self clear]; + InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:@" "]; + stateCallback(committing); + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + stateCallback(empty); + } else if (_languageModel->hasUnigramsForKey(" ")) { + _builder->insertReadingAtCursor(" "); + NSString *poppedText = [self _popOverflowComposingTextAndWalk]; + InputStateInputting *inputting = [self _buildInputtingState]; + inputting.poppedText = poppedText; + stateCallback(inputting); + } + return YES; + + } + } + InputStateChoosingCandidate *choosingCandidates = [self _buildCandidateState:(InputStateNotEmpty *) state useVerticalMode:input.useVerticalMode]; + stateCallback(choosingCandidates); + return YES; + } + + // MARK: Esc + if (charCode == 27) { + return [self _handleEscWithState:state stateCallback:stateCallback errorCallback:errorCallback]; + } + + // MARK: Cursor backward + if ([input isCursorBackward] || emacsKey == vChewingEmacsKeyBackward) { + return [self _handleBackwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; + } + + // MARK: Cursor forward + if ([input isCursorForward] || emacsKey == vChewingEmacsKeyForward) { + return [self _handleForwardWithState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; + } + + // MARK: Home + if ([input isHome] || emacsKey == vChewingEmacsKeyHome) { + return [self _handleHomeWithState:state stateCallback:stateCallback errorCallback:errorCallback]; + } + + // MARK: End + if ([input isEnd] || emacsKey == vChewingEmacsKeyEnd) { + return [self _handleEndWithState:state stateCallback:stateCallback errorCallback:errorCallback]; + } + + // MARK: AbsorbedArrowKey + if ([input isAbsorbedArrowKey] || [input isExtraChooseCandidateKey]) { + return [self _handleAbsorbedArrowKeyWithState:state stateCallback:stateCallback errorCallback:errorCallback]; + } + + // MARK: Backspace + if (charCode == 8) { + return [self _handleBackspaceWithState:state stateCallback:stateCallback errorCallback:errorCallback]; + } + + // MARK: Delete + if ([input isDelete] || emacsKey == vChewingEmacsKeyDelete) { + return [self _handleDeleteWithState:state stateCallback:stateCallback errorCallback:errorCallback]; + } + + // MARK: Enter + if (charCode == 13) { + return [self _handleEnterWithState:state stateCallback:stateCallback errorCallback:errorCallback]; + } + + // MARK: Punctuation list + if ((char) charCode == '`') { + if (_languageModel->hasUnigramsForKey(string("_punctuation_list"))) { + if (_bpmfReadingBuffer->isEmpty()) { + _builder->insertReadingAtCursor(string("_punctuation_list")); + NSString *poppedText = [self _popOverflowComposingTextAndWalk]; + InputStateInputting *inputting = [self _buildInputtingState]; + inputting.poppedText = poppedText; + stateCallback(inputting); + InputStateChoosingCandidate *choosingCandidate = [self _buildCandidateState:inputting useVerticalMode:input.useVerticalMode]; + stateCallback(choosingCandidate); + } else { // If there is still unfinished bpmf reading, ignore the punctuation + errorCallback(); + } + return YES; + } + } + + // MARK: Punctuation + // if nothing is matched, see if it's a punctuation key for current layout. + string layout = [self _currentLayout]; + string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_") : string("_punctuation_"); + string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode); + if ([self _handlePunctuation:customPunctuation state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) { + return YES; + } + + // if nothing is matched, see if it's a punctuation key. + string punctuation = punctuationNamePrefix + string(1, (char) charCode); + if ([self _handlePunctuation:punctuation state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) { + return YES; + } + + if ((char) charCode >= 'A' && (char) charCode <= 'Z') { + string letter = string("_letter_") + string(1, (char) charCode); + if ([self _handlePunctuation:letter state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) { + return YES; + } + } + + // still nothing, then we update the composing buffer (some app has + // strange behavior if we don't do this, "thinking" the key is not + // actually consumed) + if ([state isKindOfClass:[InputStateNotEmpty class]] || !_bpmfReadingBuffer->isEmpty()) { + errorCallback(); + stateCallback(state); + return YES; + } + + return NO; +} + +- (BOOL)_handleEscWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + BOOL escToClearInputBufferEnabled = Preferences.escToCleanInputBuffer; + + if (escToClearInputBufferEnabled) { + // if the option is enabled, we clear everything including the composing + // buffer, walked nodes and the reading. + [self clear]; + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + stateCallback(empty); + } else { + // if reading is not empty, we cancel the reading; Apple's built-in + // Zhuyin (and the erstwhile Hanin) has a default option that Esc + // "cancels" the current composed character and revert it to + // Bopomofo reading, in odds with the expectation of users from + // other platforms + + if (_bpmfReadingBuffer->isEmpty()) { + // no nee to beep since the event is deliberately triggered by user + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + } else { + _bpmfReadingBuffer->clear(); + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + } + } + return YES; +} + +- (BOOL)_handleBackwardWithState:(InputState *)state input:(KeyHandlerInput *)input stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + if (!_bpmfReadingBuffer->isEmpty()) { + errorCallback(); + stateCallback(state); + return YES; + } + + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + + InputStateInputting *currentState = (InputStateInputting *) state; + + if ([input isShiftHold]) { + // Shift + left + if (_builder->cursorIndex() > 0) { + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:currentState.cursorIndex - 1 readings: [self _currentReadings]]; + stateCallback(marking); + } else { + errorCallback(); + stateCallback(state); + } + } else { + if (_builder->cursorIndex() > 0) { + _builder->setCursorIndex(_builder->cursorIndex() - 1); + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + } else { + errorCallback(); + stateCallback(state); + } + } + return YES; +} + +- (BOOL)_handleForwardWithState:(InputState *)state input:(KeyHandlerInput *)input stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + if (!_bpmfReadingBuffer->isEmpty()) { + errorCallback(); + stateCallback(state); + return YES; + } + + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + + InputStateInputting *currentState = (InputStateInputting *) state; + + if ([input isShiftHold]) { + // Shift + Right + if (_builder->cursorIndex() < _builder->length()) { + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:currentState.cursorIndex + 1 readings: [self _currentReadings]]; + stateCallback(marking); + } else { + errorCallback(); + stateCallback(state); + } + } else { + if (_builder->cursorIndex() < _builder->length()) { + _builder->setCursorIndex(_builder->cursorIndex() + 1); + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + } else { + errorCallback(); + stateCallback(state); + } + } + + return YES; +} + +- (BOOL)_handleHomeWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + if (!_bpmfReadingBuffer->isEmpty()) { + errorCallback(); + stateCallback(state); + return YES; + } + + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + + if (_builder->cursorIndex()) { + _builder->setCursorIndex(0); + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + } else { + errorCallback(); + stateCallback(state); + } + + return YES; +} + +- (BOOL)_handleEndWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + if (!_bpmfReadingBuffer->isEmpty()) { + errorCallback(); + stateCallback(state); + return YES; + } + + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + + if (_builder->cursorIndex() != _builder->length()) { + _builder->setCursorIndex(_builder->length()); + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + } else { + errorCallback(); + stateCallback(state); + } + + return YES; +} + +- (BOOL)_handleAbsorbedArrowKeyWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + if (!_bpmfReadingBuffer->isEmpty()) { + errorCallback(); + } + stateCallback(state); + return YES; +} + +- (BOOL)_handleBackspaceWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + if (_bpmfReadingBuffer->isEmpty()) { + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + + if (_builder->cursorIndex()) { + _builder->deleteReadingBeforeCursor(); + [self _walk]; + } else { + errorCallback(); + stateCallback(state); + return YES; + } + } else { + _bpmfReadingBuffer->backspace(); + } + + InputStateInputting *inputting = [self _buildInputtingState]; + if (!inputting.composingBuffer.length) { + InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; + stateCallback(empty); + } else { + stateCallback(inputting); + } + return YES; +} + +- (BOOL)_handleDeleteWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + if (_bpmfReadingBuffer->isEmpty()) { + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + + if (_builder->cursorIndex() != _builder->length()) { + _builder->deleteReadingAfterCursor(); + [self _walk]; + InputStateInputting *inputting = [self _buildInputtingState]; + if (!inputting.composingBuffer.length) { + InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; + stateCallback(empty); + } else { + stateCallback(inputting); + } + } else { + errorCallback(); + stateCallback(state); + } + } else { + errorCallback(); + stateCallback(state); + } + + return YES; +} + +- (BOOL)_handleEnterWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + if ([state isKindOfClass:[InputStateInputting class]]) { + if (Preferences.useWinNT351BPMF) { + if (!_bpmfReadingBuffer->isEmpty()) { + errorCallback(); + } + return YES; + } + + [self clear]; + + InputStateInputting *current = (InputStateInputting *) state; + NSString *composingBuffer = current.composingBuffer; + InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; + stateCallback(committing); + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + stateCallback(empty); + return YES; + } + + return NO; +} + +- (BOOL)_handlePunctuation:(string)customPunctuation state:(InputState *)state usingVerticalMode:(BOOL)useVerticalMode stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback +{ + if (!_languageModel->hasUnigramsForKey(customPunctuation)) { + return NO; + } + + NSString *poppedText; + if (_bpmfReadingBuffer->isEmpty()) { + _builder->insertReadingAtCursor(customPunctuation); + poppedText = [self _popOverflowComposingTextAndWalk]; + } else { // If there is still unfinished bpmf reading, ignore the punctuation + errorCallback(); + stateCallback(state); + return YES; + } + + InputStateInputting *inputting = [self _buildInputtingState]; + inputting.poppedText = poppedText; + stateCallback(inputting); + + if (Preferences.useWinNT351BPMF && _bpmfReadingBuffer->isEmpty()) { + InputStateChoosingCandidate *candidateState = [self _buildCandidateState:inputting useVerticalMode:useVerticalMode]; + + if ([candidateState.candidates count] == 1) { + [self clear]; + InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:candidateState.candidates.firstObject]; + stateCallback(committing); + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + stateCallback(empty); + } else { + stateCallback(candidateState); + } + } + return YES; +} + + +- (BOOL)_handleMarkingState:(InputStateMarking *)state + input:(KeyHandlerInput *)input + stateCallback:(void (^)(InputState *))stateCallback + candidateSelectionCallback:(void (^)(void))candidateSelectionCallback + errorCallback:(void (^)(void))errorCallback +{ + UniChar charCode = input.charCode; + + if (charCode == 27) { + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + return YES; + } + + // Enter + if (charCode == 13) { + if (![self.delegate keyHandler:self didRequestWriteUserPhraseWithState:state]) { + errorCallback(); + return YES; + } + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + return YES; + } + + // Shift + left + if (([input isCursorBackward] || input.emacsKey == vChewingEmacsKeyBackward) + && ([input isShiftHold])) { + NSUInteger index = state.markerIndex; + if (index > 0) { + index -= 1; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings]; + stateCallback(marking); + } else { + errorCallback(); + stateCallback(state); + } + return YES; + } + + // Shift + Right + if (([input isCursorForward] || input.emacsKey == vChewingEmacsKeyForward) + && ([input isShiftHold])) { + NSUInteger index = state.markerIndex; + if (index < state.composingBuffer.length) { + index += 1; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings]; + stateCallback(marking); + } else { + errorCallback(); + stateCallback(state); + } + return YES; + } + return NO; +} + + +- (BOOL)_handleCandidateState:(InputStateChoosingCandidate *)state + input:(KeyHandlerInput *)input + stateCallback:(void (^)(InputState *))stateCallback + candidateSelectionCallback:(void (^)(void))candidateSelectionCallback + errorCallback:(void (^)(void))errorCallback; +{ + NSString *inputText = input.inputText; + UniChar charCode = input.charCode; + VTCandidateController *gCurrentCandidateController = [self.delegate candidateControllerForKeyHandler:self]; + + BOOL cancelCandidateKey = (charCode == 27) || (charCode == 8) || [input isDelete]; + + if (cancelCandidateKey) { + if (Preferences.useWinNT351BPMF) { + [self clear]; + InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; + stateCallback(empty); + } else { + InputStateInputting *inputting = [self _buildInputtingState]; + stateCallback(inputting); + } + return YES; + } + + if (charCode == 13 || [input isEnter]) { + [self.delegate keyHandler:self didSelectCandidateAtIndex:gCurrentCandidateController.selectedCandidateIndex candidateController:gCurrentCandidateController]; + return YES; + } + + if (charCode == 32 || [input isPageDown] || input.emacsKey == vChewingEmacsKeyNextPage) { + BOOL updated = [gCurrentCandidateController showNextPage]; + if (!updated) { + errorCallback(); + } + candidateSelectionCallback(); + return YES; + } + + if ([input isPageUp]) { + BOOL updated = [gCurrentCandidateController showPreviousPage]; + if (!updated) { + errorCallback(); + } + candidateSelectionCallback(); + return YES; + } + + if ([input isLeft]) { + if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { + BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; + if (!updated) { + errorCallback(); + } + } else { + BOOL updated = [gCurrentCandidateController showPreviousPage]; + if (!updated) { + errorCallback(); + } + } + candidateSelectionCallback(); + return YES; + } + + if (input.emacsKey == vChewingEmacsKeyBackward) { + BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; + if (!updated) { + errorCallback(); + } + candidateSelectionCallback(); + return YES; + } + + if ([input isRight]) { + if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { + BOOL updated = [gCurrentCandidateController highlightNextCandidate]; + if (!updated) { + errorCallback(); + } + } else { + BOOL updated = [gCurrentCandidateController showNextPage]; + if (!updated) { + errorCallback(); + } + } + candidateSelectionCallback(); + return YES; + } + + if (input.emacsKey == vChewingEmacsKeyForward) { + BOOL updated = [gCurrentCandidateController highlightNextCandidate]; + if (!updated) { + errorCallback(); + } + candidateSelectionCallback(); + return YES; + } + + if ([input isUp]) { + if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { + BOOL updated = [gCurrentCandidateController showPreviousPage]; + if (!updated) { + errorCallback(); + } + } else { + BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; + if (!updated) { + errorCallback(); + } + } + candidateSelectionCallback(); + return YES; + } + + if ([input isDown]) { + if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { + BOOL updated = [gCurrentCandidateController showNextPage]; + if (!updated) { + errorCallback(); + } + } else { + BOOL updated = [gCurrentCandidateController highlightNextCandidate]; + if (!updated) { + errorCallback(); + } + } + candidateSelectionCallback(); + return YES; + } + + if ([input isHome] || input.emacsKey == vChewingEmacsKeyHome) { + if (gCurrentCandidateController.selectedCandidateIndex == 0) { + errorCallback(); + } else { + gCurrentCandidateController.selectedCandidateIndex = 0; + } + + candidateSelectionCallback(); + return YES; + } + + if (([input isEnd] || input.emacsKey == vChewingEmacsKeyEnd) && [state.candidates count] > 0) { + if (gCurrentCandidateController.selectedCandidateIndex == [state.candidates count] - 1) { + errorCallback(); + } else { + gCurrentCandidateController.selectedCandidateIndex = [state.candidates count] - 1; + } + + candidateSelectionCallback(); + return YES; + } + + NSInteger index = NSNotFound; + for (NSUInteger j = 0, c = [gCurrentCandidateController.keyLabels count]; j < c; j++) { + if ([inputText compare:[gCurrentCandidateController.keyLabels objectAtIndex:j] options:NSCaseInsensitiveSearch] == NSOrderedSame) { + index = j; + break; + } + } + + [gCurrentCandidateController.keyLabels indexOfObject:inputText]; + + if (index != NSNotFound) { + NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:index]; + if (candidateIndex != NSUIntegerMax) { + [self.delegate keyHandler:self didSelectCandidateAtIndex:candidateIndex candidateController:gCurrentCandidateController]; + return YES; + } + } + + if (Preferences.useWinNT351BPMF) { + string layout = [self _currentLayout]; + string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_") : string("_punctuation_"); + string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode); + string punctuation = punctuationNamePrefix + string(1, (char) charCode); + + BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char) charCode) || _languageModel->hasUnigramsForKey(customPunctuation) || + _languageModel->hasUnigramsForKey(punctuation); + + if (!shouldAutoSelectCandidate && (char) charCode >= 'A' && (char) charCode <= 'Z') { + string letter = string("_letter_") + string(1, (char) charCode); + if (_languageModel->hasUnigramsForKey(letter)) { + shouldAutoSelectCandidate = YES; + } + } + + if (shouldAutoSelectCandidate) { + NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0]; + if (candidateIndex != NSUIntegerMax) { + [self.delegate keyHandler:self didSelectCandidateAtIndex:candidateIndex candidateController:gCurrentCandidateController]; + [self clear]; + InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; + stateCallback(empty); + [self handleInput:input state:empty stateCallback:stateCallback candidateSelectionCallback:candidateSelectionCallback errorCallback:errorCallback]; + } + return YES; + } + } + + errorCallback(); + candidateSelectionCallback(); + return YES; +} + +#pragma mark - States Building + +- (InputStateInputting *)_buildInputtingState +{ + // "updating the composing buffer" means to request the client to "refresh" the text input buffer + // with our "composing text" + NSMutableString *composingBuffer = [[NSMutableString alloc] init]; + NSInteger composedStringCursorIndex = 0; + + size_t readingCursorIndex = 0; + size_t builderCursorIndex = _builder->cursorIndex(); + + // we must do some Unicode codepoint counting to find the actual cursor location for the client + // i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars + // locations + for (vector::iterator wi = _walkedNodes.begin(), we = _walkedNodes.end(); wi != we; ++wi) { + if ((*wi).node) { + string nodeStr = (*wi).node->currentKeyValue().value; + vector codepoints = OVUTF8Helper::SplitStringByCodePoint(nodeStr); + size_t codepointCount = codepoints.size(); + + NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()]; + [composingBuffer appendString:valueString]; + + // this re-aligns the cursor index in the composed string + // (the actual cursor on the screen) with the builder's logical + // cursor (reading) cursor; each built node has a "spanning length" + // (e.g. two reading blocks has a spanning length of 2), and we + // accumulate those lengths to calculate the displayed cursor + // index + size_t spanningLength = (*wi).spanningLength; + if (readingCursorIndex + spanningLength <= builderCursorIndex) { + composedStringCursorIndex += [valueString length]; + readingCursorIndex += spanningLength; + } else { + for (size_t i = 0; i < codepointCount && readingCursorIndex < builderCursorIndex; i++) { + composedStringCursorIndex += [[NSString stringWithUTF8String:codepoints[i].c_str()] length]; + readingCursorIndex++; + } + } + } + } + + // now we gather all the info, we separate the composing buffer to two parts, head and tail, + // and insert the reading text (the Mandarin syllable) in between them; + // the reading text is what the user is typing + NSString *head = [composingBuffer substringToIndex:composedStringCursorIndex]; + NSString *reading = [NSString stringWithUTF8String:_bpmfReadingBuffer->composedString().c_str()]; + NSString *tail = [composingBuffer substringFromIndex:composedStringCursorIndex]; + NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; + NSInteger cursorIndex = composedStringCursorIndex + [reading length]; + + InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText cursorIndex:cursorIndex]; + return newState; +} + +- (void)_walk +{ + // retrieve the most likely trellis, i.e. a Maximum Likelihood Estimation + // of the best possible Mandarain characters given the input syllables, + // using the Viterbi algorithm implemented in the Gramambular library + Walker walker(&_builder->grid()); + + // the reverse walk traces the trellis from the end + _walkedNodes = walker.reverseWalk(_builder->grid().width()); + + // then we reverse the nodes so that we get the forward-walked nodes + reverse(_walkedNodes.begin(), _walkedNodes.end()); + + // if DEBUG is defined, a GraphViz file is written to kGraphVizOutputfile +#if DEBUG + string dotDump = _builder->grid().dumpDOT(); + NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()]; + NSError *error = nil; + + BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile atomically:YES encoding:NSUTF8StringEncoding error:&error]; +#endif +} + +- (NSString *)_popOverflowComposingTextAndWalk +{ + // in an ideal world, we can as well let the user type forever, + // but because the Viterbi algorithm has a complexity of O(N^2), + // the walk will become slower as the number of nodes increase, + // therefore we need to "pop out" overflown text -- they usually + // lose their influence over the whole MLE anyway -- so that when + // the user type along, the already composed text at front will + // be popped out + + NSString *poppedText = @""; + NSInteger composingBufferSize = Preferences.composingBufferSize; + + if (_builder->grid().width() > (size_t) composingBufferSize) { + if (_walkedNodes.size() > 0) { + NodeAnchor &anchor = _walkedNodes[0]; + poppedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()]; + _builder->removeHeadReadings(anchor.spanningLength); + } + } + + [self _walk]; + return poppedText; +} + +- (InputStateChoosingCandidate *)_buildCandidateState:(InputStateNotEmpty *)currentState useVerticalMode:(BOOL)useVerticalMode +{ + NSMutableArray *candidatesArray = [[NSMutableArray alloc] init]; + + size_t cursorIndex = [self _actualCandidateCursorIndex]; + vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); + + // sort the nodes, so that longer nodes (representing longer phrases) are placed at the top of the candidate list + stable_sort(nodes.begin(), nodes.end(), NodeAnchorDescendingSorter()); + + // then use the C++ trick to retrieve the candidates for each node at/crossing the cursor + for (vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { + const vector &candidates = (*ni).node->candidates(); + for (vector::const_iterator ci = candidates.begin(), ce = candidates.end(); ci != ce; ++ci) { + [candidatesArray addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]]; + } + } + + InputStateChoosingCandidate *state = [[InputStateChoosingCandidate alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex candidates:candidatesArray useVerticalMode:useVerticalMode]; + return state; +} + +- (size_t)_actualCandidateCursorIndex +{ + size_t cursorIndex = _builder->cursorIndex(); + if (Preferences.selectPhraseAfterCursorAsCandidate) { + // MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase + if (cursorIndex < _builder->length()) { + ++cursorIndex; + } + } else { + if (!cursorIndex) { + ++cursorIndex; + } + } + + return cursorIndex; +} + +- (NSArray *)_currentReadings +{ + NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; + vector v = _builder->readings(); + for (vector::iterator it_i = v.begin(); it_i != v.end(); ++it_i) { + [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; + } + return readingsArray; +} + +@end diff --git a/Source/Engine/ControllerModules/KeyHandlerInput.swift b/Source/Engine/ControllerModules/KeyHandlerInput.swift new file mode 100644 index 000000000..6946e3e7a --- /dev/null +++ b/Source/Engine/ControllerModules/KeyHandlerInput.swift @@ -0,0 +1,178 @@ +/* + * KeyHandlerInput.swift + * + * Copyright 2021-2022 vChewing Project (3-Clause BSD License). + * Derived from 2011-2022 OpenVanilla Project (MIT License). + * Some rights reserved. See "LICENSE.TXT" for details. + */ + +import Cocoa + +enum KeyCode: UInt16 { + case none = 0 + case enter = 76 + case up = 126 + case down = 125 + case left = 123 + case right = 124 + case pageUp = 116 + case pageDown = 121 + case home = 115 + case end = 119 + case delete = 117 +} + +class KeyHandlerInput: NSObject { + @objc private (set) var useVerticalMode: Bool + @objc private (set) var inputText: String? + @objc private (set) var charCode: UInt16 + private var keyCode: UInt16 + private var flags: NSEvent.ModifierFlags + private var cursorForwardKey: KeyCode + private var cursorBackwardKey: KeyCode + private var extraChooseCandidateKey: KeyCode + private var absorbedArrowKey: KeyCode + private var verticalModeOnlyChooseCandidateKey: KeyCode + @objc private (set) var emacsKey: vChewingEmacsKey + + @objc init(inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags, isVerticalMode: Bool) { + self.inputText = inputText + self.keyCode = keyCode + self.charCode = charCode + self.flags = flags + useVerticalMode = isVerticalMode + emacsKey = EmacsKeyHelper.detect(charCode: charCode, flags: flags) + cursorForwardKey = useVerticalMode ? .down : .right + cursorBackwardKey = useVerticalMode ? .up : .left + extraChooseCandidateKey = useVerticalMode ? .left : .down + absorbedArrowKey = useVerticalMode ? .right : .up + verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none + super.init() + } + + @objc init(event: NSEvent, isVerticalMode: Bool) { + inputText = event.characters + keyCode = event.keyCode + flags = event.modifierFlags + useVerticalMode = isVerticalMode + let charCode: UInt16 = { + guard let inputText = event.characters, inputText.count > 0 else { + return 0 + } + let first = inputText[inputText.startIndex].utf16.first! + return first + }() + self.charCode = charCode + emacsKey = EmacsKeyHelper.detect(charCode: charCode, flags: event.modifierFlags) + cursorForwardKey = useVerticalMode ? .down : .right + cursorBackwardKey = useVerticalMode ? .up : .left + extraChooseCandidateKey = useVerticalMode ? .left : .down + absorbedArrowKey = useVerticalMode ? .right : .up + verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : .none + super.init() + } + + @objc var isShiftHold: Bool { + flags.contains([.shift]) + } + + @objc var isCommandHold: Bool { + flags.contains([.command]) + } + + @objc var isControlHold: Bool { + flags.contains([.control]) + } + + @objc var isOptionHold: Bool { + flags.contains([.option]) + } + + @objc var isCapsLockOn: Bool { + flags.contains([.capsLock]) + } + + @objc var isNumericPad: Bool { + flags.contains([.numericPad]) + } + + @objc var isEnter: Bool { + KeyCode(rawValue: keyCode) == KeyCode.enter + } + + @objc var isUp: Bool { + KeyCode(rawValue: keyCode) == KeyCode.up + } + + @objc var isDown: Bool { + KeyCode(rawValue: keyCode) == KeyCode.down + } + + @objc var isLeft: Bool { + KeyCode(rawValue: keyCode) == KeyCode.left + } + + @objc var isRight: Bool { + KeyCode(rawValue: keyCode) == KeyCode.right + } + + @objc var isPageUp: Bool { + KeyCode(rawValue: keyCode) == KeyCode.pageUp + } + + @objc var isPageDown: Bool { + KeyCode(rawValue: keyCode) == KeyCode.pageDown + } + + @objc var isHome: Bool { + KeyCode(rawValue: keyCode) == KeyCode.home + } + + @objc var isEnd: Bool { + KeyCode(rawValue: keyCode) == KeyCode.end + } + + @objc var isDelete: Bool { + KeyCode(rawValue: keyCode) == KeyCode.delete + } + + @objc var isCursorBackward: Bool { + KeyCode(rawValue: keyCode) == cursorBackwardKey + } + + @objc var isCursorForward: Bool { + KeyCode(rawValue: keyCode) == cursorForwardKey + } + + @objc var isAbsorbedArrowKey: Bool { + KeyCode(rawValue: keyCode) == absorbedArrowKey + } + + @objc var isExtraChooseCandidateKey: Bool { + KeyCode(rawValue: keyCode) == extraChooseCandidateKey + } + + @objc var isVerticalModeOnlyChooseCandidateKey: Bool { + KeyCode(rawValue: keyCode) == verticalModeOnlyChooseCandidateKey + } + +} + +@objc enum vChewingEmacsKey: UInt16 { + case none = 0 + case forward = 6 // F + case backward = 2 // B + case home = 1 // A + case end = 5 // E + case delete = 4 // D + case nextPage = 22 // V +} + +class EmacsKeyHelper: NSObject { + @objc static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey { + if flags.contains(.control) { + return vChewingEmacsKey(rawValue: charCode) ?? .none + } + return .none; + } +} diff --git a/Source/Engine/vChewing/KeyValueBlobReader.cpp b/Source/Engine/ControllerModules/KeyValueBlobReader.cpp similarity index 100% rename from Source/Engine/vChewing/KeyValueBlobReader.cpp rename to Source/Engine/ControllerModules/KeyValueBlobReader.cpp diff --git a/Source/Engine/vChewing/KeyValueBlobReader.h b/Source/Engine/ControllerModules/KeyValueBlobReader.h similarity index 100% rename from Source/Engine/vChewing/KeyValueBlobReader.h rename to Source/Engine/ControllerModules/KeyValueBlobReader.h diff --git a/Source/Engine/Gramambular/BlockReadingBuilder.h b/Source/Engine/Gramambular/BlockReadingBuilder.h index af4e4b97a..1a68d8d07 100644 --- a/Source/Engine/Gramambular/BlockReadingBuilder.h +++ b/Source/Engine/Gramambular/BlockReadingBuilder.h @@ -34,9 +34,7 @@ namespace Taiyan { void setJoinSeparator(const string& separator); const string joinSeparator() const; - size_t markerCursorIndex() const; - void setMarkerCursorIndex(size_t inNewIndex); - vector readingsAtRange(size_t begin, size_t end) const; + vector readings() const; Grid& grid(); @@ -49,7 +47,6 @@ namespace Taiyan { static const size_t MaximumBuildSpanLength = 10; size_t m_cursorIndex; - size_t m_markerCursorIndex; vector m_readings; Grid m_grid; @@ -60,14 +57,12 @@ namespace Taiyan { inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel *inLM) : m_LM(inLM) , m_cursorIndex(0) - , m_markerCursorIndex(SIZE_MAX) { } inline void BlockReadingBuilder::clear() { m_cursorIndex = 0; - m_markerCursorIndex = SIZE_MAX; m_readings.clear(); m_grid.clear(); } @@ -86,21 +81,6 @@ namespace Taiyan { { m_cursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex; } - - inline size_t BlockReadingBuilder::markerCursorIndex() const - { - return m_markerCursorIndex; - } - - inline void BlockReadingBuilder::setMarkerCursorIndex(size_t inNewIndex) - { - if (inNewIndex == SIZE_MAX) { - m_markerCursorIndex = SIZE_MAX; - return; - } - - m_markerCursorIndex = inNewIndex > m_readings.size() ? m_readings.size() : inNewIndex; - } inline void BlockReadingBuilder::insertReadingAtCursor(const string& inReading) { @@ -111,12 +91,9 @@ namespace Taiyan { m_cursorIndex++; } - inline vector BlockReadingBuilder::readingsAtRange(size_t begin, size_t end) const { - vector v; - for (size_t i = begin; i < end; i++) { - v.push_back(m_readings[i]); - } - return v; + inline vector BlockReadingBuilder::readings() const + { + return m_readings; } inline bool BlockReadingBuilder::deleteReadingBeforeCursor() diff --git a/Source/Engine/Keyboard/EmacsKeyHelper.swift b/Source/Engine/Keyboard/EmacsKeyHelper.swift deleted file mode 100644 index 45e2818bf..000000000 --- a/Source/Engine/Keyboard/EmacsKeyHelper.swift +++ /dev/null @@ -1,28 +0,0 @@ -/* - * EmacsKeyHelper.swift - * - * Copyright 2021-2022 vChewing Project (3-Clause BSD License). - * Derived from 2011-2022 OpenVanilla Project (MIT License). - * Some rights reserved. See "LICENSE.TXT" for details. - */ - -import Cocoa - -@objc enum vChewingEmacsKey: UInt16 { - case none = 0 - case forward = 6 // F - case backward = 2 // B - case home = 1 // A - case end = 5 // E - case delete = 4 // D - case nextPage = 22 // V -} - -class EmacsKeyHelper: NSObject { - @objc static func detect(charCode: UniChar, flags: NSEvent.ModifierFlags) -> vChewingEmacsKey { - if flags.contains(.control) { - return vChewingEmacsKey(rawValue: charCode) ?? .none - } - return .none; - } -} diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index 75987ec23..3ae0fe355 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -8,43 +8,8 @@ #import #import -#import "Mandarin.hh" -#import "Gramambular.h" -#import "vChewingLM.h" -#import "UserOverrideModel.h" +#import "vChewing-Swift.h" @interface vChewingInputMethodController : IMKInputController -{ -@private - // the reading buffer that takes user input - Taiyan::Mandarin::BopomofoReadingBuffer* _bpmfReadingBuffer; - - // language model - vChewing::vChewingLM *_languageModel; - - // user override model - vChewing::UserOverrideModel *_userOverrideModel; - - // the grid (lattice) builder for the unigrams (and bigrams) - Taiyan::Gramambular::BlockReadingBuilder* _builder; - - // latest walked path (trellis) using the Viterbi algorithm - std::vector _walkedNodes; - - // the latest composing buffer that is updated to the foreground app - NSMutableString *_composingBuffer; - NSInteger _latestReadingCursor; - - // the current text input client; we need to keep this when candidate panel is on - id _currentCandidateClient; - - // a special deferred client for Terminal.app fix - id _currentDeferredClient; - - // currently available candidates - NSMutableArray *_candidates; - - // current input mode - NSString *_inputMode; -} +- (void)handleState:(InputState *)newState client:(id)client; @end diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index c09ba8a20..53bb1143e 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -6,125 +6,65 @@ * Some rights reserved. See "LICENSE.TXT" for details. */ +#import "vChewingLM.h" #import "InputMethodController.h" -#import -#import -#import -#import "OVUTF8Helper.h" +#import "KeyHandler.h" #import "LanguageModelManager.h" -#import "vChewing-Swift.h" -// C++ namespace usages using namespace std; -using namespace Taiyan::Mandarin; -using namespace Taiyan::Gramambular; using namespace vChewing; -using namespace OpenVanilla; static const NSInteger kMinKeyLabelSize = 10; -// input modes -static NSString *const kBopomofoModeIdentifierCHT = @"org.atelierInmu.inputmethod.vChewing.TradBopomofo"; -static NSString *const kBopomofoModeIdentifierCHS = @"org.atelierInmu.inputmethod.vChewing.SimpBopomofo"; - -// key code enums -enum { - kEnterKeyCode = 76, - kUpKeyCode = 126, - kDownKeyCode = 125, - kLeftKeyCode = 123, - kRightKeyCode = 124, - kPageUpKeyCode = 116, - kPageDownKeyCode = 121, - kHomeKeyCode = 115, - kEndKeyCode = 119, - kDeleteKeyCode = 117 -}; - VTCandidateController *gCurrentCandidateController = nil; -// if DEBUG is defined, a DOT file (GraphViz format) will be written to the -// specified path everytime the grid is walked -#if DEBUG -static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; -#endif - -// https://clang-analyzer.llvm.org/faq.html __attribute__((annotate("returns_localized_nsstring"))) static inline NSString *LocalizationNotNeeded(NSString *s) { return s; } -@interface vChewingInputMethodController (VTCandidateController) -@end - -// sort helper -class NodeAnchorDescendingSorter +@interface vChewingInputMethodController () { -public: - bool operator()(const NodeAnchor& a, const NodeAnchor &b) const { - return a.node->key().length() > b.node->key().length(); - } -}; + // the current text input client; we need to keep this when candidate panel is on + id _currentCandidateClient; -static const double kEpsilon = 0.000001; + // a special deferred client for Terminal.app fix + id _currentDeferredClient; -static double FindHighestScore(const vector& nodes, double epsilon) { - double highestScore = 0.0; - for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { - double score = ni->node->highestUnigramScore(); - if (score > highestScore) { - highestScore = score; - } - } - return highestScore + epsilon; + KeyHandler *_keyHandler; + InputState *_state; } +@end -@implementation vChewingInputMethodController -- (void)dealloc -{ - // clean up everything - if (_bpmfReadingBuffer) { - delete _bpmfReadingBuffer; - } - - if (_builder) { - delete _builder; - } - // the two client pointers are weak pointers (i.e. we don't retain them) - // therefore we don't do anything about it -} - -- (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)client -{ - // an instance is initialized whenever a text input client (a Mac app) requires - // text input from an IME - - self = [super initWithServer:server delegate:delegate client:client]; - if (self) { - _candidates = [[NSMutableArray alloc] init]; - - // create the reading buffer - _bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout()); +@interface vChewingInputMethodController (VTCandidateController) +@end - // create the lattice builder - _languageModel = [LanguageModelManager languageModelCoreCHT]; - _languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); - _languageModel->setCNSEnabled(Preferences.cns11643Enabled); - _userOverrideModel = [LanguageModelManager userOverrideModelCHT]; +@interface vChewingInputMethodController (KeyHandlerDelegate) +@end - _builder = new BlockReadingBuilder(_languageModel); +@interface vChewingInputMethodController (UI) ++ (VTHorizontalCandidateController *)horizontalCandidateController; ++ (VTVerticalCandidateController *)verticalCandidateController; ++ (TooltipController *)tooltipController; +- (void)_showTooltip:(NSString *)tooltip composingBuffer:(NSString *)composingBuffer cursorIndex:(NSInteger)cursorIndex client:(id)client; +- (void)_hideTooltip; +@end - // each Mandarin syllable is separated by a hyphen - _builder->setJoinSeparator("-"); +@implementation vChewingInputMethodController - // create the composing buffer - _composingBuffer = [[NSMutableString alloc] init]; +- (id)initWithServer:(IMKServer *)server delegate:(id)delegate client:(id)client +{ + // an instance is initialized whenever a text input client (a Mac app) requires + // text input from an IME - _inputMode = kBopomofoModeIdentifierCHT; - } + self = [super initWithServer:server delegate:delegate client:client]; + if (self) { + _keyHandler = [[KeyHandler alloc] init]; + _keyHandler.delegate = self; + _state = [[InputStateEmpty alloc] init]; + } - return self; + return self; } - (NSMenu *)menu @@ -147,7 +87,8 @@ static double FindHighestScore(const vector& nodes, double epsilon) chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; chineseConversionMenuItem.state = Preferences.chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; - NSMenuItem *halfWidthPunctuationMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Use Half-Width Punctuations", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@""]; + NSMenuItem *halfWidthPunctuationMenuItem = [menu addItemWithTitle:NSLocalizedString(@"Half-Width Punctuation Mode", @"") action:@selector(toggleHalfWidthPunctuation:) keyEquivalent:@"H"]; + halfWidthPunctuationMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; halfWidthPunctuationMenuItem.state = Preferences.halfWidthPunctuationEnabled ? NSControlStateValueOn : NSControlStateValueOff; if (optionKeyPressed) { @@ -195,1298 +136,325 @@ static double FindHighestScore(const vector& nodes, double epsilon) // reset the state _currentDeferredClient = nil; _currentCandidateClient = nil; - _builder->clear(); - _walkedNodes.clear(); - [_composingBuffer setString:@""]; - - // checks and populates the default settings - switch (Preferences.keyboardLayout) { - case KeyboardLayoutStandard: - _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::StandardLayout()); - break; - case KeyboardLayoutEten: - _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::ETenLayout()); - break; - case KeyboardLayoutHsu: - _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::HsuLayout()); - break; - case KeyboardLayoutEten26: - _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::ETen26Layout()); - break; - case KeyboardLayoutHanyuPinyin: - _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::HanyuPinyinLayout()); - break; - case KeyboardLayoutIBM: - _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::IBMLayout()); - break; - default: - _bpmfReadingBuffer->setKeyboardLayout(BopomofoKeyboardLayout::StandardLayout()); - Preferences.keyboardLayout = KeyboardLayoutStandard; - } + [_keyHandler clear]; + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + [self handleState:empty client:client]; - [(AppDelegate *)[NSApp delegate] checkForUpdate]; + // checks and populates the default settings + [_keyHandler syncWithPreferences]; + [(AppDelegate *) NSApp.delegate checkForUpdate]; } - (void)deactivateServer:(id)client { - // clean up reading buffer residues - if (!_bpmfReadingBuffer->isEmpty()) { - _bpmfReadingBuffer->clear(); - [client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - } - - // commit any residue in the composing buffer - [self commitComposition:client]; - - _currentDeferredClient = nil; - _currentCandidateClient = nil; - - gCurrentCandidateController.delegate = nil; - gCurrentCandidateController.visible = NO; - [_candidates removeAllObjects]; - - [self _hideTooltip]; + [_keyHandler clear]; + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + [self handleState:empty client:client]; + InputStateDeactivated *inactive = [[InputStateDeactivated alloc] init]; + [self handleState:inactive client:client]; } - (void)setValue:(id)value forTag:(long)tag client:(id)sender { NSString *newInputMode; - vChewingLM *newLanguageModel; - UserOverrideModel *newUserOverrideModel; if ([value isKindOfClass:[NSString class]] && [value isEqual:kBopomofoModeIdentifierCHS]) { newInputMode = kBopomofoModeIdentifierCHS; - newLanguageModel = [LanguageModelManager languageModelCoreCHS]; - newUserOverrideModel = [LanguageModelManager userOverrideModelCHS]; } else { newInputMode = kBopomofoModeIdentifierCHT; - newLanguageModel = [LanguageModelManager languageModelCoreCHT]; - newUserOverrideModel = [LanguageModelManager userOverrideModelCHT]; } + + if (![_keyHandler.inputMode isEqualToString:newInputMode]) { + [[NSUserDefaults standardUserDefaults] synchronize]; - // 自 Preferences 模組讀入自訂語彙置換功能開關狀態。 - newLanguageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); + // Remember to override the keyboard layout again -- treat this as an activate event. + NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; + [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; + [_keyHandler clear]; + _keyHandler.inputMode = newInputMode; + InputState *empty = [[InputState alloc] init]; + [self handleState:empty client:sender]; + } - // 自 Preferences 模組讀取全字庫模式開關狀態。 - newLanguageModel->setCNSEnabled(Preferences.cns11643Enabled); - - // Only apply the changes if the value is changed - if (![_inputMode isEqualToString:newInputMode]) { - [[NSUserDefaults standardUserDefaults] synchronize]; - - // Remember to override the keyboard layout again -- treat this as an activate eventy - NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; - [sender overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - - _inputMode = newInputMode; - _languageModel = newLanguageModel; - _userOverrideModel = newUserOverrideModel; - - if (!_bpmfReadingBuffer->isEmpty()) { - _bpmfReadingBuffer->clear(); - [self updateClientComposingBuffer:sender]; - } - - if ([_composingBuffer length] > 0) { - [self commitComposition:sender]; - } - - if (_builder) { - delete _builder; - _builder = new BlockReadingBuilder(_languageModel); - _builder->setJoinSeparator("-"); - } - } } #pragma mark - IMKServerInput protocol methods -- (NSString *)_convertToKangXi:(NSString *)text +- (NSUInteger)recognizedEvents:(id)sender { - // return [VXHanConvert convertToSimplifiedFrom:text]; // VXHanConvert 這個引擎有點落後了,不支援詞組轉換、且修改轉換表的過程很麻煩。 - // OpenCC 引擎別的都還好,就是有點肥。改日換成純 ObjC 的 OpenCC 實現方案。 - return [OpenCCBridge convertToKangXi:text]; + return NSEventMaskKeyDown | NSEventMaskFlagsChanged; } -- (void)commitComposition:(id)client +- (BOOL)handleEvent:(NSEvent *)event client:(id)client { - // if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper) - // then we defer the update in the next runloop round -- so that the composing buffer is not - // meaninglessly flushed, an annoying bug in Terminal.app since Mac OS X 10.5 - if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) { - if (_currentDeferredClient) { - [self performSelector:@selector(updateClientComposingBuffer:) withObject:_currentDeferredClient afterDelay:0.0]; - } - return; - } - - // Chinese conversion. - NSString *buffer = _composingBuffer; - - if (Preferences.chineseConversionEnabled) { - buffer = [self _convertToKangXi:_composingBuffer]; - } - - // commit the text, clear the state - [client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - _builder->clear(); - _walkedNodes.clear(); - [_composingBuffer setString:@""]; - gCurrentCandidateController.visible = NO; - [_candidates removeAllObjects]; - [self _hideTooltip]; -} + if ([event type] == NSEventMaskFlagsChanged) { + NSString *functionKeyKeyboardLayoutID = Preferences.functionKeyboardLayout; + NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; + + // If no override is needed, just return NO. + if ([functionKeyKeyboardLayoutID isEqualToString:basisKeyboardLayoutID]) { + return NO; + } + + // Function key pressed. + BOOL includeShift = Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey; + if ((event.modifierFlags & ~NSEventModifierFlagShift) || ((event.modifierFlags & NSEventModifierFlagShift) && includeShift)) { + // Override the keyboard layout and let the OS do its thing + [client overrideKeyboardWithKeyboardNamed:functionKeyKeyboardLayoutID]; + return NO; + } + + // Revert to the basis layout when the function key is released + [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; + return NO; + } + + NSRect textFrame = NSZeroRect; + NSDictionary *attributes = nil; + BOOL useVerticalMode = NO; + + @try { + attributes = [client attributesForCharacterIndex:0 lineHeightRectangle:&textFrame]; + useVerticalMode = attributes[@"IMKTextOrientation"] && [attributes[@"IMKTextOrientation"] integerValue] == 0; + } + @catch (NSException *e) { + // exception may raise while using Twitter.app's search filed. + } + + if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) { + // special handling for com.apple.Terminal + _currentDeferredClient = client; + } + + KeyHandlerInput *input = [[KeyHandlerInput alloc] initWithEvent:event isVerticalMode:useVerticalMode]; + BOOL result = [_keyHandler handleInput:input state:_state stateCallback:^(InputState *state) { + [self handleState:state client:client]; + } candidateSelectionCallback:^{ + NSLog(@"candidate window updated."); + } errorCallback:^{ + [clsSFX beep]; + }]; + + return result; +} + +#pragma mark - States Handling -NS_INLINE size_t min(size_t a, size_t b) { return a < b ? a : b; } -NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - -// TODO: bug #28 is more likely to live in this method. -- (void)updateClientComposingBuffer:(id)client +- (NSString *)_convertToKangXi:(NSString *)text { - // "updating the composing buffer" means to request the client to "refresh" the text input buffer - // with our "composing text" - - [_composingBuffer setString:@""]; - NSInteger composedStringCursorIndex = 0; - - size_t readingCursorIndex = 0; - size_t builderCursorIndex = _builder->cursorIndex(); - - // we must do some Unicode codepoint counting to find the actual cursor location for the client - // i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars - // locations - for (vector::iterator wi = _walkedNodes.begin(), we = _walkedNodes.end() ; wi != we ; ++wi) { - if ((*wi).node) { - string nodeStr = (*wi).node->currentKeyValue().value; - vector codepoints = OVUTF8Helper::SplitStringByCodePoint(nodeStr); - size_t codepointCount = codepoints.size(); - - NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()]; - [_composingBuffer appendString:valueString]; - - // this re-aligns the cursor index in the composed string - // (the actual cursor on the screen) with the builder's logical - // cursor (reading) cursor; each built node has a "spanning length" - // (e.g. two reading blocks has a spanning length of 2), and we - // accumulate those lengthes to calculate the displayed cursor - // index - size_t spanningLength = (*wi).spanningLength; - if (readingCursorIndex + spanningLength <= builderCursorIndex) { - composedStringCursorIndex += [valueString length]; - readingCursorIndex += spanningLength; - } - else { - for (size_t i = 0; i < codepointCount && readingCursorIndex < builderCursorIndex; i++) { - composedStringCursorIndex += [[NSString stringWithUTF8String:codepoints[i].c_str()] length]; - readingCursorIndex++; - } - } - } - } - - // now we gather all the info, we separate the composing buffer to two parts, head and tail, - // and insert the reading text (the Mandarin syllable) in between them; - // the reading text is what the user is typing - NSString *head = [_composingBuffer substringToIndex:composedStringCursorIndex]; - NSString *reading = [NSString stringWithUTF8String:_bpmfReadingBuffer->composedString().c_str()]; - NSString *tail = [_composingBuffer substringFromIndex:composedStringCursorIndex]; - NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; - NSInteger cursorIndex = composedStringCursorIndex + [reading length]; - - if (_bpmfReadingBuffer->isEmpty() && _builder->markerCursorIndex() != SIZE_MAX) { - // if there is a marked range, we need to tear the string into three parts. - NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText]; - size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); - size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); - [attrString setAttributes:@{ - NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), - NSMarkedClauseSegmentAttributeName: @0 - } range:NSMakeRange(0, begin)]; - [attrString setAttributes:@{ - NSUnderlineStyleAttributeName: @(NSUnderlineStyleThick), - NSMarkedClauseSegmentAttributeName: @1 - } range:NSMakeRange(begin, end - begin)]; - [attrString setAttributes:@{ - NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), - NSMarkedClauseSegmentAttributeName: @2 - } range:NSMakeRange(end, [composedText length] - end)]; - // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, - // i.e. the client app needs to take care of where to put ths composing buffer - [client setMarkedText:attrString selectionRange:NSMakeRange((NSInteger)_builder->markerCursorIndex(), 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - _latestReadingCursor = (NSInteger)_builder->markerCursorIndex(); - [self _showCurrentMarkedTextTooltipWithClient:client]; - } - else { - // we must use NSAttributedString so that the cursor is visible -- - // can't just use NSString - NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), - NSMarkedClauseSegmentAttributeName: @0}; - NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:composedText attributes:attrDict]; - - // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, - // i.e. the client app needs to take care of where to put ths composing buffer - [client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - _latestReadingCursor = cursorIndex; - [self _hideTooltip]; + if (!Preferences.chineseConversionEnabled) { + return text; // 沒啟用的話就不要轉換。 } + // return [VXHanConvert convertToSimplifiedFrom:text]; // VXHanConvert 這個引擎有點落後了,不支援詞組轉換、且修改轉換表的過程很麻煩。 + // OpenCC 引擎別的都還好,就是有點肥。改日換成純 ObjC 的 OpenCC 實現方案。 + return [OpenCCBridge convertToKangXi:text]; } -- (void)walk +- (void)_commitText:(NSString *)text client:(id)client { - // retrieve the most likely trellis, i.e. a Maximum Likelihood Estimation - // of the best possible Mandarain characters given the input syllables, - // using the Viterbi algorithm implemented in the Gramambular library - Walker walker(&_builder->grid()); + NSString *buffer = [self _convertToKangXi:text]; + if (!buffer.length) { + return;; + } - // the reverse walk traces the trellis from the end - _walkedNodes = walker.reverseWalk(_builder->grid().width()); - - // then we reverse the nodes so that we get the forward-walked nodes - reverse(_walkedNodes.begin(), _walkedNodes.end()); - - // if DEBUG is defined, a GraphViz file is written to kGraphVizOutputfile -#if DEBUG - string dotDump = _builder->grid().dumpDOT(); - NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()]; - NSError *error = nil; - - BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile atomically:YES encoding:NSUTF8StringEncoding error:&error]; -#endif + // if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper) + // then we defer the update in the next runloop round -- so that the composing buffer is not + // meaninglessly flushed, an annoying bug in Terminal.app since Mac OS X 10.5 + if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) { + if (_currentDeferredClient) { + id currentDeferredClient = _currentDeferredClient; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [currentDeferredClient insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; + }); + } + return; + } + [client insertText:buffer replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; } -- (void)popOverflowComposingTextAndWalk:(id)client +- (void)handleState:(InputState *)newState client:(id)client { - // in an ideal world, we can as well let the user type forever, - // but because the Viterbi algorithm has a complexity of O(N^2), - // the walk will become slower as the number of nodes increase, - // therefore we need to "pop out" overflown text -- they usually - // lose their influence over the whole MLE anyway -- so tht when - // the user type along, the already composed text at front will - // be popped out - - NSInteger composingBufferSize = Preferences.composingBufferSize; - - if (_builder->grid().width() > (size_t)composingBufferSize) { - if (_walkedNodes.size() > 0) { - NodeAnchor &anchor = _walkedNodes[0]; - NSString *popedText = [NSString stringWithUTF8String:anchor.node->currentKeyValue().value.c_str()]; - // Chinese conversion. - BOOL chineseConversionEnabled = Preferences.chineseConversionEnabled; - if (chineseConversionEnabled) { - popedText = [self _convertToKangXi:popedText]; - } - [client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - _builder->removeHeadReadings(anchor.spanningLength); - } - } +// NSLog(@"new state: %@ / current state: %@", newState, _state); - [self walk]; -} + // We need to set the state to the member variable since the candidate + // window need to read the candidates from it. + InputState *previous = _state; + _state = newState; -- (void)beep -{ - // use the vChewing beep. - [clsSFX beep]; + if ([newState isKindOfClass:[InputStateDeactivated class]]) { + [self _handleDeactivated:(InputStateDeactivated *) newState previous:previous client:client]; + } else if ([newState isKindOfClass:[InputStateEmpty class]]) { + [self _handleEmpty:(InputStateEmpty *) newState previous:previous client:client]; + } else if ([newState isKindOfClass:[InputStateEmptyIgnoringPreviousState class]]) { + [self _handleEmptyIgnoringPrevious:(InputStateEmptyIgnoringPreviousState *) newState previous:previous client:client]; + } else if ([newState isKindOfClass:[InputStateCommitting class]]) { + [self _handleCommitting:(InputStateCommitting *) newState previous:previous client:client]; + } else if ([newState isKindOfClass:[InputStateInputting class]]) { + [self _handleInputting:(InputStateInputting *) newState previous:previous client:client]; + } else if ([newState isKindOfClass:[InputStateMarking class]]) { + [self _handleMarking:(InputStateMarking *) newState previous:previous client:client]; + } else if ([newState isKindOfClass:[InputStateChoosingCandidate class]]) { + [self _handleChoosingCandidate:(InputStateChoosingCandidate *) newState previous:previous client:client]; + } } -- (string)_currentLayout +- (void)_handleDeactivated:(InputStateDeactivated *)state previous:(InputState *)previous client:(id)client { - NSString *keyboardLayoutName = Preferences.keyboardLayoutName; - string layout = string(keyboardLayoutName.UTF8String) + string("_"); - return layout; -} + // commit any residue in the composing buffer + if ([previous isKindOfClass:[InputStateInputting class]]) { + NSString *buffer = ((InputStateInputting *) previous).composingBuffer; + [self _commitText:buffer client:client]; + } + [client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; -- (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client -{ - NSRect textFrame = NSZeroRect; - NSDictionary *attributes = nil; + _currentDeferredClient = nil; + _currentCandidateClient = nil; - bool composeReading = false; - BOOL useVerticalMode = NO; - - @try { - attributes = [client attributesForCharacterIndex:0 lineHeightRectangle:&textFrame]; - useVerticalMode = [attributes objectForKey:@"IMKTextOrientation"] && [[attributes objectForKey:@"IMKTextOrientation"] integerValue] == 0; - } - @catch (NSException *e) { - // exception may raise while using Twitter.app's search filed. - } - - NSInteger cursorForwardKey = useVerticalMode ? kDownKeyCode : kRightKeyCode; - NSInteger cursorBackwardKey = useVerticalMode ? kUpKeyCode : kLeftKeyCode; - NSInteger extraChooseCandidateKey = useVerticalMode ? kLeftKeyCode : kDownKeyCode; - NSInteger absorbedArrowKey = useVerticalMode ? kRightKeyCode : kUpKeyCode; - NSInteger verticalModeOnlyChooseCandidateKey = useVerticalMode ? absorbedArrowKey : 0; - - // get the unicode character code - UniChar charCode = [inputText length] ? [inputText characterAtIndex:0] : 0; - - vChewingEmacsKey emacsKey = [EmacsKeyHelper detectWithCharCode:charCode flags:flags]; - - if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && [NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) { - // special handling for com.apple.Terminal - _currentDeferredClient = client; - } - - // if the inputText is empty, it's a function key combination, we ignore it - if (![inputText length]) { - return NO; - } - - // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it - if (![_composingBuffer length] && - _bpmfReadingBuffer->isEmpty() && - ((flags & NSEventModifierFlagCommand) || (flags & NSEventModifierFlagControl) || (flags & NSEventModifierFlagOption) || (flags & NSEventModifierFlagNumericPad))) { - return NO; - } - - // Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo. - if (charCode == 8 || charCode == 13 || keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey || keyCode == cursorForwardKey || keyCode == cursorBackwardKey) { - // do nothing if backspace is pressed -- we ignore the key - } - else if (flags & NSAlphaShiftKeyMask) { - // process all possible combination, we hope. - if ([_composingBuffer length]) { - [self commitComposition:client]; - } - - // first commit everything in the buffer. - if (flags & NSEventModifierFlagShift) { - return NO; - } - - // if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char insertions. - if (charCode < 0x80 && !isprint(charCode)) { - return NO; - } - - // when shift is pressed, don't do further processing, since it outputs capital letter anyway. - NSString *popedText = [inputText lowercaseString]; - [client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - return YES; - } - - if (flags & NSEventModifierFlagNumericPad) { - if (keyCode != kLeftKeyCode && keyCode != kRightKeyCode && keyCode != kDownKeyCode && keyCode != kUpKeyCode && charCode != 32 && isprint(charCode)) { - if ([_composingBuffer length]) { - [self commitComposition:client]; - } - - NSString *popedText = [inputText lowercaseString]; - [client insertText:popedText replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - return YES; - } - } - - // if we have candidate, it means we need to pass the event to the candidate handler - if ([_candidates count]) { - return [self _handleCandidateEventWithInputText:inputText charCode:charCode keyCode:keyCode emacsKey:(vChewingEmacsKey)emacsKey]; - } - - // If we have marker index. - if (_builder->markerCursorIndex() != SIZE_MAX) { - // ESC - if (charCode == 27) { - _builder->setMarkerCursorIndex(SIZE_MAX); - [self updateClientComposingBuffer:client]; - return YES; - } - // Enter - if (charCode == 13) { - if ([self _writeUserPhrase]) { - _builder->setMarkerCursorIndex(SIZE_MAX); - } - else { - [self beep]; - } - [self updateClientComposingBuffer:client]; - return YES; - } - // Shift + left - if ((keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) - && (flags & NSEventModifierFlagShift)) { - if (_builder->markerCursorIndex() > 0) { - _builder->setMarkerCursorIndex(_builder->markerCursorIndex() - 1); - } - else { - [self beep]; - } - [self updateClientComposingBuffer:client]; - return YES; - } - // Shift + Right - if ((keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) - && (flags & NSEventModifierFlagShift)) { - if (_builder->markerCursorIndex() < _builder->length()) { - _builder->setMarkerCursorIndex(_builder->markerCursorIndex() + 1); - } - else { - [self beep]; - } - [self updateClientComposingBuffer:client]; - return YES; - } - - _builder->setMarkerCursorIndex(SIZE_MAX); - } - - // see if it's valid BPMF reading - if (_bpmfReadingBuffer->isValidKey((char)charCode)) { - _bpmfReadingBuffer->combineKey((char)charCode); - - // if we have a tone marker, we have to insert the reading to the - // builder in other words, if we don't have a tone marker, we just - // update the composing buffer - composeReading = _bpmfReadingBuffer->hasToneMarker(); - if (!composeReading) { - [self updateClientComposingBuffer:client]; - return YES; - } - } - - // see if we have composition if Enter/Space is hit and buffer is not empty - // this is bit-OR'ed so that the tone marker key is also taken into account - composeReading |= (!_bpmfReadingBuffer->isEmpty() && (charCode == 32 || charCode == 13)); - if (composeReading) { - // combine the reading - string reading = _bpmfReadingBuffer->syllable().composedString(); - - // see if we have a unigram for this - if (!_languageModel->hasUnigramsForKey(reading)) { - [self beep]; - [self updateClientComposingBuffer:client]; - return YES; - } - - // and insert it into the lattice - _builder->insertReadingAtCursor(reading); - - // then walk the lattice - [self popOverflowComposingTextAndWalk:client]; - - // get user override model suggestion - string overrideValue = _userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); - - if (!overrideValue.empty()) { - size_t cursorIndex = [self actualCandidateCursorIndex]; - vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); - double highestScore = FindHighestScore(nodes, kEpsilon); - _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, highestScore); - } - - // then update the text - _bpmfReadingBuffer->clear(); - [self updateClientComposingBuffer:client]; - - // 模擬 WINNT 351 ㄅ半注音,就是每個漢字都自動要選字的那種注音。 - // 嚴格來講不能算純正的ㄅ半注音,畢竟候選字的順序不可能會像當年那樣了。 - // 如果簡體中文用戶不知道ㄅ半注音是什麼的話,拿全拼輸入法來比喻恐怕比較恰當。 - if (Preferences.useWinNT351BPMF) { - [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; - } - - // and tells the client that the key is consumed - return YES; - } - - // keyCode 125 = Down, charCode 32 = Space - if (_bpmfReadingBuffer->isEmpty() && [_composingBuffer length] > 0 && (keyCode == extraChooseCandidateKey || charCode == 32 || (useVerticalMode && (keyCode == verticalModeOnlyChooseCandidateKey)))) { - if (charCode == 32) { - // if the spacebar is NOT set to be a selection key - if ((flags & NSEventModifierFlagShift) != 0 || !Preferences.chooseCandidateUsingSpace) { - if (_builder->cursorIndex() >= _builder->length()) { - [_composingBuffer appendString:@" "]; - [self commitComposition:client]; - _bpmfReadingBuffer->clear(); - } - else if (_languageModel->hasUnigramsForKey(" ")) { - _builder->insertReadingAtCursor(" "); - [self popOverflowComposingTextAndWalk:client]; - [self updateClientComposingBuffer:client]; - } - return YES; - - } - } - [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; - return YES; - } - - // Esc - if (charCode == 27) { - BOOL escToClearInputBufferEnabled = Preferences.escToCleanInputBuffer; - - if (escToClearInputBufferEnabled) { - // if the optioon is enabled, we clear everythiong including the composing - // buffer, walked nodes and the reading. - if (![_composingBuffer length]) { - return NO; - } - _bpmfReadingBuffer->clear(); - _builder->clear(); - _walkedNodes.clear(); - [_composingBuffer setString:@""]; - } - else { - // if reading is not empty, we cancel the reading; Apple's built-in - // Zhuyin (and the erstwhile Hanin) has a default option that Esc - // "cancels" the current composed character and revert it to - // Bopomofo reading, in odds with the expectation of users from - // other platforms - - if (_bpmfReadingBuffer->isEmpty()) { - // no nee to beep since the event is deliberately triggered by user - - if (![_composingBuffer length]) { - return NO; - } - } - else { - _bpmfReadingBuffer->clear(); - } - } - - [self updateClientComposingBuffer:client]; - return YES; - } - - // handle cursor backward - if (keyCode == cursorBackwardKey || emacsKey == vChewingEmacsKeyBackward) { - if (!_bpmfReadingBuffer->isEmpty()) { - [self beep]; - } - else { - if (![_composingBuffer length]) { - return NO; - } - - if (flags & NSEventModifierFlagShift) { - // Shift + left - if (_builder->cursorIndex() > 0) { - _builder->setMarkerCursorIndex(_builder->cursorIndex() - 1); - } - else { - [self beep]; - } - } else { - if (_builder->cursorIndex() > 0) { - _builder->setCursorIndex(_builder->cursorIndex() - 1); - } - else { - [self beep]; - } - } - } - - [self updateClientComposingBuffer:client]; - return YES; - } - - // handle cursor forward - if (keyCode == cursorForwardKey || emacsKey == vChewingEmacsKeyForward) { - if (!_bpmfReadingBuffer->isEmpty()) { - [self beep]; - } - else { - if (![_composingBuffer length]) { - return NO; - } - - if (flags & NSEventModifierFlagShift) { - // Shift + Right - if (_builder->cursorIndex() < _builder->length()) { - _builder->setMarkerCursorIndex(_builder->cursorIndex() + 1); - } else { - [self beep]; - } - } else { - if (_builder->cursorIndex() < _builder->length()) { - _builder->setCursorIndex(_builder->cursorIndex() + 1); - } - else { - [self beep]; - } - } - } - - [self updateClientComposingBuffer:client]; - return YES; - } - - if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) { - if (!_bpmfReadingBuffer->isEmpty()) { - [self beep]; - } - else { - if (![_composingBuffer length]) { - return NO; - } - - if (_builder->cursorIndex()) { - _builder->setCursorIndex(0); - } - else { - [self beep]; - } - } - - [self updateClientComposingBuffer:client]; - return YES; - } - - if (keyCode == kEndKeyCode || emacsKey == vChewingEmacsKeyEnd) { - if (!_bpmfReadingBuffer->isEmpty()) { - [self beep]; - } - else { - if (![_composingBuffer length]) { - return NO; - } - - if (_builder->cursorIndex() != _builder->length()) { - _builder->setCursorIndex(_builder->length()); - } - else { - [self beep]; - } - } - - [self updateClientComposingBuffer:client]; - return YES; - } - - if (keyCode == absorbedArrowKey || keyCode == extraChooseCandidateKey) { - if (!_bpmfReadingBuffer->isEmpty()) { - [self beep]; - } - [self updateClientComposingBuffer:client]; - return YES; - } - - // Backspace - if (charCode == 8) { - if (_bpmfReadingBuffer->isEmpty()) { - if (![_composingBuffer length]) { - return NO; - } - - if (_builder->cursorIndex()) { - _builder->deleteReadingBeforeCursor(); - [self walk]; - } - else { - [self beep]; - } - } - else { - _bpmfReadingBuffer->backspace(); - } - - [self updateClientComposingBuffer:client]; - return YES; - } - - // Delete - if (keyCode == kDeleteKeyCode || emacsKey == vChewingEmacsKeyDelete) { - if (_bpmfReadingBuffer->isEmpty()) { - if (![_composingBuffer length]) { - return NO; - } - - if (_builder->cursorIndex() != _builder->length()) { - _builder->deleteReadingAfterCursor(); - [self walk]; - } - else { - [self beep]; - } - } - else { - [self beep]; - } - - [self updateClientComposingBuffer:client]; - return YES; - } - - // Enter - if (charCode == 13) { - if (![_composingBuffer length]) { - return NO; - } - - [self commitComposition:client]; - return YES; - } - - // punctuation list - if ((char)charCode == '`') { - if (_languageModel->hasUnigramsForKey(string("_punctuation_list"))) { - if (_bpmfReadingBuffer->isEmpty()) { - _builder->insertReadingAtCursor(string("_punctuation_list")); - [self popOverflowComposingTextAndWalk:client]; - [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; - } - else { // If there is still unfinished bpmf reading, ignore the punctuation - [self beep]; - } - [self updateClientComposingBuffer:client]; - return YES; - } - } - - // if nothing is matched, see if it's a punctuation key for current layout. - string layout = [self _currentLayout]; - string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_"): string("_punctuation_"); - string customPunctuation = punctuationNamePrefix + layout + string(1, (char)charCode); - if ([self _handlePunctuation:customPunctuation usingVerticalMode:useVerticalMode client:client]) { - return YES; - } - - // if nothing is matched, see if it's a punctuation key. - string punctuation = punctuationNamePrefix + string(1, (char)charCode); - if ([self _handlePunctuation:punctuation usingVerticalMode:useVerticalMode client:client]) { - return YES; - } - - if ((char)charCode >= 'A' && (char)charCode <= 'Z') { - if ([_composingBuffer length]) { - string letter = string("_letter_") + string(1, (char)charCode); - if ([self _handlePunctuation:letter usingVerticalMode:useVerticalMode client:client]) { - return YES; - } - } - } - - // still nothing, then we update the composing buffer (some app has - // strange behavior if we don't do this, "thinking" the key is not - // actually consumed) - if ([_composingBuffer length] || !_bpmfReadingBuffer->isEmpty()) { - [self beep]; - [self updateClientComposingBuffer:client]; - return YES; - } - - return NO; + gCurrentCandidateController.delegate = nil; + gCurrentCandidateController.visible = NO; + [self _hideTooltip]; } -- (BOOL)_handlePunctuation:(string)customPunctuation usingVerticalMode:(BOOL)useVerticalMode client:(id)client +- (void)_handleEmpty:(InputStateEmpty *)state previous:(InputState *)previous client:(id)client { - if (_languageModel->hasUnigramsForKey(customPunctuation)) { - if (_bpmfReadingBuffer->isEmpty()) { - _builder->insertReadingAtCursor(customPunctuation); - [self popOverflowComposingTextAndWalk:client]; - } - else { // If there is still unfinished bpmf reading, ignore the punctuation - [self beep]; - } - [self updateClientComposingBuffer:client]; - - if (Preferences.useWinNT351BPMF && _bpmfReadingBuffer->isEmpty()) { - [self collectCandidates]; - if ([_candidates count] == 1) { - [self commitComposition:client]; - } - else { - [self _showCandidateWindowUsingVerticalMode:useVerticalMode client:client]; - } - } - return YES; - } - return NO; -} + // commit any residue in the composing buffer + if ([previous isKindOfClass:[InputStateInputting class]]) { + NSString *buffer = ((InputStateInputting *) previous).composingBuffer; + [self _commitText:buffer client:client]; + } -- (BOOL)_handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode emacsKey:(vChewingEmacsKey)emacsKey -{ - BOOL cancelCandidateKey = - (charCode == 27) || - (Preferences.useWinNT351BPMF && - (charCode == 8 || keyCode == kDeleteKeyCode)); - - if (cancelCandidateKey) { - gCurrentCandidateController.visible = NO; - [_candidates removeAllObjects]; - - if (Preferences.useWinNT351BPMF) { - _builder->clear(); - _walkedNodes.clear(); - [_composingBuffer setString:@""]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else if (charCode == 13 || keyCode == kEnterKeyCode) { - [self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:gCurrentCandidateController.selectedCandidateIndex]; - return YES; - } - else if (charCode == 32 || keyCode == kPageDownKeyCode || emacsKey == vChewingEmacsKeyNextPage) { - BOOL updated = [gCurrentCandidateController showNextPage]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else if (keyCode == kPageUpKeyCode) { - BOOL updated = [gCurrentCandidateController showPreviousPage]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else if (keyCode == kLeftKeyCode) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { - BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else { - BOOL updated = [gCurrentCandidateController showPreviousPage]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - } - else if (emacsKey == vChewingEmacsKeyBackward) { - BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else if (keyCode == kRightKeyCode) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { - BOOL updated = [gCurrentCandidateController highlightNextCandidate]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else { - BOOL updated = [gCurrentCandidateController showNextPage]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - } - else if (emacsKey == vChewingEmacsKeyForward) { - BOOL updated = [gCurrentCandidateController highlightNextCandidate]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else if (keyCode == kUpKeyCode) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { - BOOL updated = [gCurrentCandidateController showPreviousPage]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else { - BOOL updated = [gCurrentCandidateController highlightPreviousCandidate]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - } - else if (keyCode == kDownKeyCode) { - if ([gCurrentCandidateController isKindOfClass:[VTHorizontalCandidateController class]]) { - BOOL updated = [gCurrentCandidateController showNextPage]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else { - BOOL updated = [gCurrentCandidateController highlightNextCandidate]; - if (!updated) { - [self beep]; - } - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - } - else if (keyCode == kHomeKeyCode || emacsKey == vChewingEmacsKeyHome) { - if (gCurrentCandidateController.selectedCandidateIndex == 0) { - [self beep]; - - } - else { - gCurrentCandidateController.selectedCandidateIndex = 0; - } - - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else if ((keyCode == kEndKeyCode || emacsKey == vChewingEmacsKeyEnd) && [_candidates count] > 0) { - if (gCurrentCandidateController.selectedCandidateIndex == [_candidates count] - 1) { - [self beep]; - } - else { - gCurrentCandidateController.selectedCandidateIndex = [_candidates count] - 1; - } - - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } - else { - NSInteger index = NSNotFound; - for (NSUInteger j = 0, c = [gCurrentCandidateController.keyLabels count]; j < c; j++) { - if ([inputText compare:[gCurrentCandidateController.keyLabels objectAtIndex:j] options:NSCaseInsensitiveSearch] == NSOrderedSame) { - index = j; - break; - } - } - - [gCurrentCandidateController.keyLabels indexOfObject:inputText]; - if (index != NSNotFound) { - NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:index]; - if (candidateIndex != NSUIntegerMax) { - [self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:candidateIndex]; - return YES; - } - } - - if (Preferences.useWinNT351BPMF) { - string layout = [self _currentLayout]; - string customPunctuation = string("_punctuation_") + layout + string(1, (char)charCode); - string punctuation = string("_punctuation_") + string(1, (char)charCode); - - BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || _languageModel->hasUnigramsForKey(customPunctuation) || - _languageModel->hasUnigramsForKey(punctuation); - - if (shouldAutoSelectCandidate) { - NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0]; - if (candidateIndex != NSUIntegerMax) { - [self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:candidateIndex]; - return [self handleInputText:inputText key:keyCode modifiers:0 client:_currentCandidateClient]; - } - } - } - - [self beep]; - [self updateClientComposingBuffer:_currentCandidateClient]; - return YES; - } + [client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; + gCurrentCandidateController.visible = NO; + [self _hideTooltip]; } -- (NSUInteger)recognizedEvents:(id)sender +- (void)_handleEmptyIgnoringPrevious:(InputStateEmptyIgnoringPreviousState *)state previous:(InputState *)previous client:(id)client { - return NSKeyDownMask | NSFlagsChangedMask; + [client setMarkedText:@"" selectionRange:NSMakeRange(0, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; + gCurrentCandidateController.visible = NO; + [self _hideTooltip]; } -- (BOOL)handleEvent:(NSEvent *)event client:(id)client +- (void)_handleCommitting:(InputStateCommitting *)state previous:(InputState *)previous client:(id)client { - if ([event type] == NSFlagsChanged) { - NSString *functionKeyKeyboardLayoutID = Preferences.functionKeyboardLayout; - NSString *basisKeyboardLayoutID = Preferences.basisKeyboardLayout; - - // If no override is needed, just return NO. - if ([functionKeyKeyboardLayoutID isEqualToString:basisKeyboardLayoutID]) { - return NO; - } - - // Function key pressed. - BOOL includeShift = Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey; - if (([event modifierFlags] & ~NSEventModifierFlagShift) || (([event modifierFlags] & NSEventModifierFlagShift) && includeShift)) { - // Override the keyboard layout and let the OS do its thing - [client overrideKeyboardWithKeyboardNamed:functionKeyKeyboardLayoutID]; - return NO; - } - - // Revert back to the basis layout when the function key is released - [client overrideKeyboardWithKeyboardNamed:basisKeyboardLayoutID]; - return NO; - } - - NSString *inputText = [event characters]; - NSInteger keyCode = [event keyCode]; - NSUInteger flags = [event modifierFlags]; - return [self handleInputText:inputText key:keyCode modifiers:flags client:client]; + NSString *poppedText = state.poppedText; + [self _commitText:poppedText client:client]; + gCurrentCandidateController.visible = NO; + [self _hideTooltip]; } -#pragma mark - Private methods - -+ (VTHorizontalCandidateController *)horizontalCandidateController +- (void)_handleInputting:(InputStateInputting *)state previous:(InputState *)previous client:(id)client { - static VTHorizontalCandidateController *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[VTHorizontalCandidateController alloc] init]; - }); - return instance; -} + NSString *poppedText = state.poppedText; + if (poppedText.length) { + [self _commitText:poppedText client:client]; + } -+ (VTVerticalCandidateController *)verticalCandidateController -{ - static VTVerticalCandidateController *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[VTVerticalCandidateController alloc] init]; - }); - return instance; -} + NSUInteger cursorIndex = state.cursorIndex; + NSAttributedString *attrString = state.attributedString; -+ (TooltipController *)tooltipController -{ - static TooltipController *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[TooltipController alloc] init]; - }); - return instance; + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, + // i.e. the client app needs to take care of where to put ths composing buffer + [client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; + + gCurrentCandidateController.visible = NO; + [self _hideTooltip]; } -- (void)collectCandidates +- (void)_handleMarking:(InputStateMarking *)state previous:(InputState *)previous client:(id)client { - // returns the candidate - [_candidates removeAllObjects]; + NSUInteger cursorIndex = state.cursorIndex; + NSAttributedString *attrString = state.attributedString; - size_t cursorIndex = [self actualCandidateCursorIndex]; - vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, + // i.e. the client app needs to take care of where to put ths composing buffer + [client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - // sort the nodes, so that longer nodes (representing longer phrases) are placed at the top of the candidate list - stable_sort(nodes.begin(), nodes.end(), NodeAnchorDescendingSorter()); - - // then use the C++ trick to retrieve the candidates for each node at/crossing the cursor - for (vector::iterator ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { - const vector& candidates = (*ni).node->candidates(); - for (vector::const_iterator ci = candidates.begin(), ce = candidates.end(); ci != ce; ++ci) { - [_candidates addObject:[NSString stringWithUTF8String:(*ci).value.c_str()]]; - } - } + gCurrentCandidateController.visible = NO; + if (state.tooltip.length) { + [self _showTooltip:state.tooltip composingBuffer:state.composingBuffer cursorIndex:state.markerIndex client:client]; + } else { + [self _hideTooltip]; + } } -- (size_t)actualCandidateCursorIndex +- (void)_handleChoosingCandidate:(InputStateChoosingCandidate *)state previous:(InputState *)previous client:(id)client { - size_t cursorIndex = _builder->cursorIndex(); - if (Preferences.selectPhraseAfterCursorAsCandidate) { - // MS Phonetics IME style, phrase is *after* the cursor, i.e. cursor is always *before* the phrase - if (cursorIndex < _builder->length()) { - ++cursorIndex; - } - } - else { - if (!cursorIndex) { - ++cursorIndex; - } - } - - return cursorIndex; -} + NSUInteger cursorIndex = state.cursorIndex; + NSAttributedString *attrString = state.attributedString; -- (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client -{ - // set the candidate panel style + // the selection range is where the cursor is, with the length being 0 and replacement range NSNotFound, + // i.e. the client app needs to take care of where to put ths composing buffer + [client setMarkedText:attrString selectionRange:NSMakeRange(cursorIndex, 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; - if (useVerticalMode) { - gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; - } - else if (Preferences.useHorizontalCandidateList) { - gCurrentCandidateController = [vChewingInputMethodController horizontalCandidateController]; - } - else { - gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; - } - - // set the attributes for the candidate panel (which uses NSAttributedString) - NSInteger textSize = Preferences.candidateListTextSize; - - NSInteger keyLabelSize = textSize / 2; - if (keyLabelSize < kMinKeyLabelSize) { - keyLabelSize = kMinKeyLabelSize; - } - - NSString *ctFontName = Preferences.candidateTextFontName; - NSString *klFontName = Preferences.candidateKeyLabelFontName; - NSString *ckeys = Preferences.candidateKeys; - - gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont systemFontOfSize:keyLabelSize]; - gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize]; - - NSMutableArray *keyLabels = [NSMutableArray arrayWithObjects:@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", nil]; - - if ([ckeys length] > 1) { - [keyLabels removeAllObjects]; - for (NSUInteger i = 0, c = [ckeys length]; i < c; i++) { - [keyLabels addObject:[ckeys substringWithRange:NSMakeRange(i, 1)]]; - } - } - - gCurrentCandidateController.keyLabels = keyLabels; - [self collectCandidates]; - - if (Preferences.useWinNT351BPMF && [_candidates count] == 1) { - [self commitComposition:client]; - return; - } - - gCurrentCandidateController.delegate = self; - [gCurrentCandidateController reloadData]; - - // update the composing text, set the client - [self updateClientComposingBuffer:client]; - _currentCandidateClient = client; - - NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); - - NSInteger cursor = _latestReadingCursor; - if (cursor == [_composingBuffer length] && cursor != 0) { - cursor--; - } - - // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch - @try { - [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; - } - @catch (NSException *exception) { - NSLog(@"lineHeightRectangle %@", exception); - } - - if (useVerticalMode) { - [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; - } - else { - [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; - } - - gCurrentCandidateController.visible = YES; + if (![previous isKindOfClass:[InputStateChoosingCandidate class]]) { + [self _showCandidateWindowWithState:state client:client]; + } } -#pragma mark - User phrases - -- (NSString *)_currentMarkedText +- (void)_showCandidateWindowWithState:(InputStateChoosingCandidate *)state client:(id)client { - if (_builder->markerCursorIndex() < 0) { - return @""; - } - if (!_bpmfReadingBuffer->isEmpty()) { - return @""; - } + // set the candidate panel style + BOOL useVerticalMode = state.useVerticalMode; - size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); - size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); - // A phrase should contain at least two characters. - if (end - begin < 1) { - return @""; - } + if (useVerticalMode) { + gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; + } else if (Preferences.useHorizontalCandidateList) { + gCurrentCandidateController = [vChewingInputMethodController horizontalCandidateController]; + } else { + gCurrentCandidateController = [vChewingInputMethodController verticalCandidateController]; + } - NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); - NSString *selectedText = [_composingBuffer substringWithRange:range]; - return selectedText; -} + // set the attributes for the candidate panel (which uses NSAttributedString) + NSInteger textSize = Preferences.candidateListTextSize; -- (NSString *)_currentMarkedTextAndReadings -{ - if (_builder->markerCursorIndex() < 0) { - return @""; - } - if (!_bpmfReadingBuffer->isEmpty()) { - return @""; - } + NSInteger keyLabelSize = textSize / 2; + if (keyLabelSize < kMinKeyLabelSize) { + keyLabelSize = kMinKeyLabelSize; + } - size_t begin = min(_builder->markerCursorIndex(), _builder->cursorIndex()); - size_t end = max(_builder->markerCursorIndex(), _builder->cursorIndex()); - // A phrase should contain at least two characters. - if (end - begin < 2) { - return @""; - } - if (end - begin > Preferences.maxCandidateLength) { - return @""; - } + NSString *ctFontName = Preferences.candidateTextFontName; + NSString *klFontName = Preferences.candidateKeyLabelFontName; + NSString *candidateKeys = Preferences.candidateKeys; - NSRange range = NSMakeRange((NSInteger)begin, (NSInteger)(end - begin)); - NSString *selectedText = [_composingBuffer substringWithRange:range]; - NSMutableString *string = [[NSMutableString alloc] init]; - [string appendString:selectedText]; - [string appendString:@" "]; - NSMutableArray *readingsArray = [[NSMutableArray alloc] init]; - vector v = _builder->readingsAtRange(begin, end); - for(vector::iterator it_i=v.begin(); it_i!=v.end(); ++it_i) { - [readingsArray addObject:[NSString stringWithUTF8String:it_i->c_str()]]; - } - [string appendString:[readingsArray componentsJoinedByString:@"-"]]; - return string; -} + gCurrentCandidateController.keyLabelFont = klFontName ? [NSFont fontWithName:klFontName size:keyLabelSize] : [NSFont systemFontOfSize:keyLabelSize]; + gCurrentCandidateController.candidateFont = ctFontName ? [NSFont fontWithName:ctFontName size:textSize] : [NSFont systemFontOfSize:textSize]; -- (BOOL)_writeUserPhrase -{ - NSString *currentMarkedPhrase = [self _currentMarkedTextAndReadings]; - if (![currentMarkedPhrase length]) { - [self beep]; - return NO; - } - - return [LanguageModelManager writeUserPhrase:currentMarkedPhrase inputMode:_inputMode]; -} + NSMutableArray *keyLabels = [@[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"] mutableCopy]; -- (void)_showCurrentMarkedTextTooltipWithClient:(id)client -{ - NSString *text = [self _currentMarkedText]; - NSInteger length = text.length; - if (!length) { - [self _hideTooltip]; - } - else if (Preferences.phraseReplacementEnabled) { - NSString *message = [NSString stringWithFormat:NSLocalizedString(@"⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", @""), text]; - [self _showTooltip:message client:client]; - } - else if (length < 2) { - NSString *message = [NSString stringWithFormat:NSLocalizedString(@"\"%@\" length must ≥ 2 for a user phrase.", @""), text]; - [self _showTooltip:message client:client]; - } - else if (length > Preferences.maxCandidateLength) { - NSString *message = [NSString stringWithFormat:NSLocalizedString(@"\"%@\" length too long for a user phrase.", @""), text]; - [self _showTooltip:message client:client]; - } - else { - NSString *message = [NSString stringWithFormat:NSLocalizedString(@"\"%@\" selected. ENTER to add user phrase.", @""), text]; - [self _showTooltip:message client:client]; - } -} + if (candidateKeys.length > 1) { + [keyLabels removeAllObjects]; + for (NSUInteger i = 0, c = candidateKeys.length; i < c; i++) { + [keyLabels addObject:[candidateKeys substringWithRange:NSMakeRange(i, 1)]]; + } + } -- (void)_showTooltip:(NSString *)tooltip client:(id)client -{ - NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); + gCurrentCandidateController.keyLabels = keyLabels; + gCurrentCandidateController.delegate = self; + [gCurrentCandidateController reloadData]; + _currentCandidateClient = client; - NSInteger cursor = _latestReadingCursor; - if (cursor == [_composingBuffer length] && cursor != 0) { - cursor--; - } + NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); + NSInteger cursor = state.cursorIndex; + if (cursor == state.composingBuffer.length && cursor != 0) { + cursor--; + } - // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch - @try { - [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; - } - @catch (NSException *exception) { - NSLog(@"lineHeightRectangle %@", exception); - } + // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch + @try { + [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; + } + @catch (NSException *exception) { + NSLog(@"lineHeightRectangle %@", exception); + } - [[vChewingInputMethodController tooltipController] showTooltip:tooltip atPoint:lineHeightRect.origin]; -} + if (useVerticalMode) { + [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x + lineHeightRect.size.width + 4.0, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; + } else { + [gCurrentCandidateController setWindowTopLeftPoint:NSMakePoint(lineHeightRect.origin.x, lineHeightRect.origin.y - 4.0) bottomOutOfScreenAdjustmentHeight:lineHeightRect.size.height + 4.0]; + } -- (void)_hideTooltip -{ - if ([vChewingInputMethodController tooltipController].window.isVisible) { - [[vChewingInputMethodController tooltipController] hide]; - } + gCurrentCandidateController.visible = YES; } #pragma mark - Misc menu items @@ -1517,28 +485,26 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)toggleHalfWidthPunctuation:(id)sender { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-result" - [Preferences toggleHalfWidthPunctuationEnabled]; -#pragma GCC diagnostic pop + [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"Half-Width Punctuation Mode", @""), @"\n", [Preferences toggleHalfWidthPunctuationEnabled] ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; } - (void)togglePhraseReplacementEnabled:(id)sender { - if (_inputMode == kBopomofoModeIdentifierCHT) { - BOOL enabled = [Preferences togglePhraseReplacementEnabled]; - vChewingLM *lm = [LanguageModelManager languageModelCoreCHT]; - lm->setPhraseReplacementEnabled(enabled); - } else { - BOOL enabled = [Preferences togglePhraseReplacementEnabled]; - vChewingLM *lm = [LanguageModelManager languageModelCoreCHS]; - lm->setPhraseReplacementEnabled(enabled); - } + if (_keyHandler.inputMode == kBopomofoModeIdentifierCHT){ + [LanguageModelManager languageModelCoreCHT]->setPhraseReplacementEnabled([Preferences togglePhraseReplacementEnabled]); + + } else { + [LanguageModelManager languageModelCoreCHS]->setPhraseReplacementEnabled([Preferences togglePhraseReplacementEnabled]); + } } - (void)toggleCNS11643Enabled:(id)sender { - _languageModel->setCNSEnabled([Preferences toggleCNS11643Enabled]); + if (_keyHandler.inputMode == kBopomofoModeIdentifierCHT){ + [LanguageModelManager languageModelCoreCHT]->setCNSEnabled([Preferences toggleCNS11643Enabled]); + } else { + [LanguageModelManager languageModelCoreCHS]->setCNSEnabled([Preferences toggleCNS11643Enabled]); + } // 注意上面這一行已經動過開關了,所以接下來就不要 toggle。 [NotifierController notifyWithMessage:[NSString stringWithFormat:@"%@%@%@", NSLocalizedString(@"CNS11643 Mode", @""), @"\n", [Preferences cns11643Enabled] ? NSLocalizedString(@"NotificationSwitchON", @"") : NSLocalizedString(@"NotificationSwitchOFF", @"")] stay:NO]; } @@ -1551,18 +517,18 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)checkForUpdate:(id)sender { - [(AppDelegate *)[[NSApplication sharedApplication] delegate] checkForUpdateForced:YES]; + [(AppDelegate *) NSApp.delegate checkForUpdateForced:YES]; } - (BOOL)_checkUserFiles { - if (![LanguageModelManager checkIfUserLanguageModelFilesExist] ) { - NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), [LanguageModelManager dataFolderPath]]; - [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; - return NO; - } + if (![LanguageModelManager checkIfUserLanguageModelFilesExist]) { + NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), [LanguageModelManager dataFolderPath]]; + [[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; + return NO; + } - return YES; + return YES; } - (void)_openUserFile:(NSString *)path @@ -1575,17 +541,17 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (void)openUserPhrases:(id)sender { - [self _openUserFile:[LanguageModelManager userPhrasesDataPath:_inputMode]]; + [self _openUserFile:[LanguageModelManager userPhrasesDataPath:_keyHandler.inputMode]]; } - (void)openExcludedPhrases:(id)sender { - [self _openUserFile:[LanguageModelManager excludedPhrasesDataPath:_inputMode]]; + [self _openUserFile:[LanguageModelManager excludedPhrasesDataPath:_keyHandler.inputMode]]; } - (void)openPhraseReplacement:(id)sender { - [self _openUserFile:[LanguageModelManager phraseReplacementDataPath:_inputMode]]; + [self _openUserFile:[LanguageModelManager phraseReplacementDataPath:_keyHandler.inputMode]]; } - (void)reloadUserPhrases:(id)sender @@ -1609,37 +575,132 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; } - (NSUInteger)candidateCountForController:(VTCandidateController *)controller { - return [_candidates count]; + if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) { + InputStateChoosingCandidate *state = (InputStateChoosingCandidate *) _state; + return state.candidates.count; + } + return 0; } - (NSString *)candidateController:(VTCandidateController *)controller candidateAtIndex:(NSUInteger)index { - return [_candidates objectAtIndex:index]; + if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) { + InputStateChoosingCandidate *state = (InputStateChoosingCandidate *) _state; + return state.candidates[index]; + } + return @""; } - (void)candidateController:(VTCandidateController *)controller didSelectCandidateAtIndex:(NSUInteger)index { - gCurrentCandidateController.visible = NO; + gCurrentCandidateController.visible = NO; - // candidate selected, override the node with selection - string selectedValue = [[_candidates objectAtIndex:index] UTF8String]; + if ([_state isKindOfClass:[InputStateChoosingCandidate class]]) { + InputStateChoosingCandidate *state = (InputStateChoosingCandidate *) _state; - size_t cursorIndex = [self actualCandidateCursorIndex]; - _builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue); - if (!Preferences.useWinNT351BPMF) { - _userOverrideModel->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); - } + // candidate selected, override the node with selection + string selectedValue = [state.candidates[index] UTF8String]; + [_keyHandler fixNodeWithValue:selectedValue]; + InputStateInputting *inputting = [_keyHandler _buildInputtingState]; + + if (Preferences.useWinNT351BPMF) { + [_keyHandler clear]; + InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:inputting.composingBuffer]; + [self handleState:committing client:_currentCandidateClient]; + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + [self handleState:empty client:_currentCandidateClient]; + } else { + [self handleState:inputting client:_currentCandidateClient]; + } + } +} - [_candidates removeAllObjects]; +@end - [self walk]; - [self updateClientComposingBuffer:_currentCandidateClient]; +#pragma mark - Implementation - if (Preferences.useWinNT351BPMF) { - [self commitComposition:_currentCandidateClient]; - return; - } +@implementation vChewingInputMethodController (KeyHandlerDelegate) + +- (nonnull VTCandidateController *)candidateControllerForKeyHandler:(nonnull KeyHandler *)keyHandler +{ + return gCurrentCandidateController; +} + +- (BOOL)keyHandler:(nonnull KeyHandler *)keyHandler didRequestWriteUserPhraseWithState:(nonnull InputStateMarking *)state +{ + if (!state.validToWrite) { + return NO; + } + NSString *userPhrase = state.userPhrase; + return [LanguageModelManager writeUserPhrase:userPhrase inputMode:_keyHandler.inputMode]; + return YES; +} + +- (void)keyHandler:(nonnull KeyHandler *)keyHandler didSelectCandidateAtIndex:(NSInteger)index candidateController:(nonnull VTCandidateController *)controller +{ + [self candidateController:gCurrentCandidateController didSelectCandidateAtIndex:index]; } @end + +@implementation vChewingInputMethodController (UI) + ++ (VTHorizontalCandidateController *)horizontalCandidateController +{ + static VTHorizontalCandidateController *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[VTHorizontalCandidateController alloc] init]; + }); + return instance; +} + ++ (VTVerticalCandidateController *)verticalCandidateController +{ + static VTVerticalCandidateController *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[VTVerticalCandidateController alloc] init]; + }); + return instance; +} + ++ (TooltipController *)tooltipController +{ + static TooltipController *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[TooltipController alloc] init]; + }); + return instance; +} + +- (void)_showTooltip:(NSString *)tooltip composingBuffer:(NSString *)composingBuffer cursorIndex:(NSInteger)cursorIndex client:(id)client +{ + NSRect lineHeightRect = NSMakeRect(0.0, 0.0, 16.0, 16.0); + + NSUInteger cursor = (NSUInteger) cursorIndex; + if (cursor == composingBuffer.length && cursor != 0) { + cursor--; + } + + // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch + @try { + [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; + } + @catch (NSException *exception) { + NSLog(@"%@", exception); + } + + [[vChewingInputMethodController tooltipController] showTooltip:tooltip atPoint:lineHeightRect.origin]; +} + +- (void)_hideTooltip +{ + if ([vChewingInputMethodController tooltipController].window.isVisible) { + [[vChewingInputMethodController tooltipController] hide]; + } +} + +@end diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index c70b20d94..ceefa187c 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -7,15 +7,9 @@ */ #import "LanguageModelManager.h" -#import -#import -#import -#import "OVUTF8Helper.h" using namespace std; -using namespace Taiyan::Gramambular; using namespace vChewing; -using namespace OpenVanilla; static const int kUserOverrideModelCapacity = 500; static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. @@ -72,7 +66,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing NSLog(@"User Data Folder N/A."); return NO; } - if (![self checkIfFileExist:[self cnsDataPath]]) { + if (![self ensureFileExists:[self cnsDataPath]]) { NSLog(@"Extracted CNS Data Not Found."); return NO; } @@ -122,7 +116,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing return YES; } -+ (BOOL)checkIfFileExist:(NSString *)filePath ++ (BOOL)ensureFileExists:(NSString *)filePath { if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES]; @@ -139,22 +133,22 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing if (![self checkIfUserDataFolderExists]) { return NO; } - if (![self checkIfFileExist:[self userPhrasesDataPath:kBopomofoModeIdentifierCHT]]) { + if (![self ensureFileExists:[self userPhrasesDataPath:kBopomofoModeIdentifierCHT]]) { return NO; } - if (![self checkIfFileExist:[self excludedPhrasesDataPath:kBopomofoModeIdentifierCHT]]) { + if (![self ensureFileExists:[self excludedPhrasesDataPath:kBopomofoModeIdentifierCHT]]) { return NO; } - if (![self checkIfFileExist:[self phraseReplacementDataPath:kBopomofoModeIdentifierCHT]]) { + if (![self ensureFileExists:[self phraseReplacementDataPath:kBopomofoModeIdentifierCHT]]) { return NO; } - if (![self checkIfFileExist:[self userPhrasesDataPath:kBopomofoModeIdentifierCHS]]) { + if (![self ensureFileExists:[self userPhrasesDataPath:kBopomofoModeIdentifierCHS]]) { return NO; } - if (![self checkIfFileExist:[self excludedPhrasesDataPath:kBopomofoModeIdentifierCHS]]) { + if (![self ensureFileExists:[self excludedPhrasesDataPath:kBopomofoModeIdentifierCHS]]) { return NO; } - if (![self checkIfFileExist:[self phraseReplacementDataPath:kBopomofoModeIdentifierCHS]]) { + if (![self ensureFileExists:[self phraseReplacementDataPath:kBopomofoModeIdentifierCHS]]) { return NO; } return YES; @@ -188,7 +182,7 @@ static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, vChewing + (NSString *)dataFolderPath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES); - NSString *appSupportPath = [paths objectAtIndex:0]; + NSString *appSupportPath = paths[0]; NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"]; return userDictPath; } diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 0732f0daa..26ba4bb10 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -21,9 +21,9 @@ "Unable to create the user phrase file." = "Unable to create the user phrase file."; "Please check the permission of at \"%@\"." = "Please check the permission of at \"%@\"."; "Edit Excluded Phrases" = "Edit Excluded Phrases…"; -"Use Half-Width Punctuations" = "Use Half-Width Punctuations"; +"Half-Width Punctuation Mode" = "Half-Width Punctuation Mode"; "\"%@\" length must ≥ 2 for a user phrase." = "\"%@\" length must ≥ 2 for a user phrase."; -"\"%@\" length too long for a user phrase." = "\"%@\" length too long for a user phrase."; +"\"%@\" length should ≤ %d for a user phrase." = "\"%@\" length should ≤ %d for a user phrase."; "\"%@\" selected. ENTER to add user phrase." = "\"%@\" selected. ENTER to add user phrase."; "Edit Phrase Replacement Table" = "Edit Phrase Replacement Table…"; "Use Phrase Replacement" = "Use Phrase Replacement"; diff --git a/Source/ja.lproj/Localizable.strings b/Source/ja.lproj/Localizable.strings index a7a60c75d..73d5cbee0 100644 --- a/Source/ja.lproj/Localizable.strings +++ b/Source/ja.lproj/Localizable.strings @@ -21,9 +21,9 @@ "Unable to create the user phrase file." = "ユーザー辞書ファイルの作成は失敗しました。"; "Please check the permission of at \"%@\"." = "「%@」に書き出す権限は不足らしい。"; "Edit Excluded Phrases" = "辞書条目排除表を編集…"; -"Use Half-Width Punctuations" = "半角句読機能を起用"; +"Half-Width Punctuation Mode" = "半角句読モード"; "\"%@\" length must ≥ 2 for a user phrase." = "「%@」もう1つ文字のお選びを。"; -"\"%@\" length too long for a user phrase." = "「%@」文字数過剰で登録不可。"; +"\"%@\" length should ≤ %d for a user phrase." = "「%@」文字数過剰で登録不可、%d 文字以内にして下さい。"; "\"%@\" selected. ENTER to add user phrase." = "「%@」を ENTER で辞書に登録。"; "Edit Phrase Replacement Table" = "言葉置換表を編集…"; "Use Phrase Replacement" = "言葉置換機能"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index e9926ac7f..7a3e14c11 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -21,9 +21,9 @@ "Unable to create the user phrase file." = "无法创建自订语汇档案。"; "Please check the permission of at \"%@\"." = "请检查此处的存取权限:\"%@\"."; "Edit Excluded Phrases" = "编辑要滤除的语汇…"; -"Use Half-Width Punctuations" = "啟用半角標點輸出"; +"Half-Width Punctuation Mode" = "半角标点模式"; "\"%@\" length must ≥ 2 for a user phrase." = "「%@」字数不足以自订语汇。"; -"\"%@\" length too long for a user phrase." = "「%@」字数太长、无法自订。"; +"\"%@\" length should ≤ %d for a user phrase." = "「%@」字数超过 %d、无法自订。"; "\"%@\" selected. ENTER to add user phrase." = "「%@」敲 Enter 添入自订语汇。"; "Edit Phrase Replacement Table" = "编辑语汇置换表…"; "Use Phrase Replacement" = "使用语汇置换"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index aac48d3f3..1066c72fe 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -21,9 +21,9 @@ "Unable to create the user phrase file." = "無法創建自訂語彙檔案。"; "Please check the permission of at \"%@\"." = "請檢查此處的存取權限:\"%@\"."; "Edit Excluded Phrases" = "編輯要濾除的語彙…"; -"Use Half-Width Punctuations" = "啟用半形標點輸出"; +"Half-Width Punctuation Mode" = "半形標點模式"; "\"%@\" length must ≥ 2 for a user phrase." = "「%@」字數不足以自訂語彙。"; -"\"%@\" length too long for a user phrase." = "「%@」字數太長、無法自訂。"; +"\"%@\" length should ≤ %d for a user phrase." = "「%@」字數超過 %d、無法自訂。"; "\"%@\" selected. ENTER to add user phrase." = "「%@」敲 Enter 添入自訂語彙。"; "Edit Phrase Replacement Table" = "編輯語彙置換表…"; "Use Phrase Replacement" = "使用語彙置換"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index abe9cde9f..172e274ff 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -23,12 +23,14 @@ 5B810D9F27A3A5E50032C1A9 /* LMConsolidator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B810D9D27A3A5E50032C1A9 /* LMConsolidator.mm */; }; 5B9DD0A927A7950D00ED335A /* FSEventStreamHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9DD0A827A7950D00ED335A /* FSEventStreamHelper.swift */; }; 5BC2D2882793B434002C0BEC /* KeyValueBlobReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */; }; - 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */; }; 5BC2D28D2793B98F002C0BEC /* PreferencesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */; }; 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */; }; 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */; }; 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC3EE1A278FC48C00F5E44C /* HorizontalCandidateController.swift */; }; 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC3FB82278492DE0022E99A /* data-chs.txt */; }; + 5BC772AA27A5A1E800CA8391 /* InputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC772A927A5A1E800CA8391 /* InputState.swift */; }; + 5BC772AC27A5A31200CA8391 /* KeyHandlerInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC772AB27A5A31200CA8391 /* KeyHandlerInput.swift */; }; + 5BC772B027A5B3AB00CA8391 /* KeyHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5BC772AD27A5A31B00CA8391 /* KeyHandler.mm */; }; 5BD0D19427940E9D0008F48E /* Fart.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19327940E9D0008F48E /* Fart.aif */; }; 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0D19927943D390008F48E /* clsSFX.swift */; }; 5BD0D19F279454F60008F48E /* Beep.aif in Resources */ = {isa = PBXBuildFile; fileRef = 5BD0D19E279454F60008F48E /* Beep.aif */; }; @@ -129,7 +131,6 @@ 5BA923AC2791B7C20001323A /* vChewingInstaller-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewingInstaller-Bridging-Header.h"; sourceTree = ""; }; 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyValueBlobReader.h; sourceTree = ""; }; 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeyValueBlobReader.cpp; sourceTree = ""; }; - 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmacsKeyHelper.swift; sourceTree = ""; }; 5BC2D28C2793B98F002C0BEC /* PreferencesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesModule.swift; sourceTree = ""; }; 5BC3EE18278FC48C00F5E44C /* VerticalCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalCandidateController.swift; sourceTree = ""; }; 5BC3EE19278FC48C00F5E44C /* VTCandidateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VTCandidateController.swift; sourceTree = ""; }; @@ -144,6 +145,10 @@ 5BC4BC5B2796E6A90023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = Source/ja.lproj/InfoPlist.strings; sourceTree = ""; }; 5BC4BC5C2796E6A90023BBD5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = Source/ja.lproj/Localizable.strings; sourceTree = ""; }; 5BC4BC5E2796F5C40023BBD5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/preferences.strings; sourceTree = ""; }; + 5BC772A927A5A1E800CA8391 /* InputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputState.swift; sourceTree = ""; }; + 5BC772AB27A5A31200CA8391 /* KeyHandlerInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerInput.swift; sourceTree = ""; }; + 5BC772AD27A5A31B00CA8391 /* KeyHandler.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyHandler.mm; sourceTree = ""; }; + 5BC772AE27A5A31B00CA8391 /* KeyHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyHandler.h; sourceTree = ""; }; 5BD0D19327940E9D0008F48E /* Fart.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = Fart.aif; sourceTree = ""; }; 5BD0D19927943D390008F48E /* clsSFX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = clsSFX.swift; sourceTree = ""; }; 5BD0D19E279454F60008F48E /* Beep.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.aif; sourceTree = ""; }; @@ -271,21 +276,17 @@ path = LanguageModel; sourceTree = ""; }; - 5BC2D2832793B434002C0BEC /* vChewing */ = { + 5BC2D2832793B434002C0BEC /* ControllerModules */ = { isa = PBXGroup; children = ( + 5BC772AE27A5A31B00CA8391 /* KeyHandler.h */, + 5BC772AD27A5A31B00CA8391 /* KeyHandler.mm */, + 5BC772AB27A5A31200CA8391 /* KeyHandlerInput.swift */, 5BC2D2842793B434002C0BEC /* KeyValueBlobReader.h */, 5BC2D2862793B434002C0BEC /* KeyValueBlobReader.cpp */, + 5BC772A927A5A1E800CA8391 /* InputState.swift */, ); - path = vChewing; - sourceTree = ""; - }; - 5BC2D2892793B8DB002C0BEC /* Keyboard */ = { - isa = PBXGroup; - children = ( - 5BC2D28A2793B8FB002C0BEC /* EmacsKeyHelper.swift */, - ); - path = Keyboard; + path = ControllerModules; sourceTree = ""; }; 5BD0D19827943D270008F48E /* SFX */ = { @@ -452,8 +453,7 @@ isa = PBXGroup; children = ( 5BD0D19827943D270008F48E /* SFX */, - 5BC2D2892793B8DB002C0BEC /* Keyboard */, - 5BC2D2832793B434002C0BEC /* vChewing */, + 5BC2D2832793B434002C0BEC /* ControllerModules */, 5BA8DAFE27928120009C9FFF /* LanguageModel */, 6A0D4F1315FC0EB100ABF4B3 /* Gramambular */, ); @@ -711,7 +711,7 @@ buildActionMask = 2147483647; files = ( 5BDF2CFE2791BE4400838ADB /* InputSourceHelper.swift in Sources */, - 5BC2D28B2793B8FB002C0BEC /* EmacsKeyHelper.swift in Sources */, + 5BC772AC27A5A31200CA8391 /* KeyHandlerInput.swift in Sources */, 5BDD25F5279D6CFE00AA18F8 /* AWFileHash.m in Sources */, 5BDD25F8279D6D1200AA18F8 /* zip.m in Sources */, 5BD0D19A27943D390008F48E /* clsSFX.swift in Sources */, @@ -727,6 +727,7 @@ 5BE798A42792E58A00337FF9 /* TooltipController.swift in Sources */, 5BDD25F6279D6D0200AA18F8 /* SSZipArchive.m in Sources */, 5BDF2D062791DFF200838ADB /* AppDelegate.swift in Sources */, + 5BC772B027A5B3AB00CA8391 /* KeyHandler.mm in Sources */, 5BC3EE1B278FC48C00F5E44C /* VerticalCandidateController.swift in Sources */, 5B42B64027876FDC00BB9B9F /* UserOverrideModel.mm in Sources */, 5BDD25F7279D6D1200AA18F8 /* unzip.m in Sources */, @@ -743,6 +744,7 @@ 5BC3EE1C278FC48C00F5E44C /* VTCandidateController.swift in Sources */, 5BDF2D032791C71200838ADB /* NonModalAlertWindowController.swift in Sources */, 5BC3EE1D278FC48C00F5E44C /* HorizontalCandidateController.swift in Sources */, + 5BC772AA27A5A1E800CA8391 /* InputState.swift in Sources */, 5BDD25FD279D6D6300AA18F8 /* CNSLM.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; -- Gitee From e77f253ed60c72f791c16ec01979d0011fe9aba7 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 1 Feb 2022 14:09:33 +0800 Subject: [PATCH 143/163] Zonble: UPR265 to let Shift+Space commit current comp. buffer. --- Source/Engine/ControllerModules/KeyHandler.mm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Engine/ControllerModules/KeyHandler.mm b/Source/Engine/ControllerModules/KeyHandler.mm index 195afee6c..236e869e9 100644 --- a/Source/Engine/ControllerModules/KeyHandler.mm +++ b/Source/Engine/ControllerModules/KeyHandler.mm @@ -364,7 +364,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // if the spacebar is NOT set to be a selection key if ([input isShiftHold] || !Preferences.chooseCandidateUsingSpace) { if (_builder->cursorIndex() >= _builder->length()) { - [self clear]; + if ([state isKindOfClass:[InputStateNotEmpty class]]) { + NSString *composingBuffer = [(InputStateNotEmpty *)state composingBuffer]; + if ([composingBuffer length]) { + InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; + stateCallback(committing); + } + } + [self clear]; InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:@" "]; stateCallback(committing); InputStateEmpty *empty = [[InputStateEmpty alloc] init]; -- Gitee From a2792f62795f8417b96f841f34865d6a4d899129 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 1 Feb 2022 14:14:00 +0800 Subject: [PATCH 144/163] Zonble: Ctrl-key punctuation support. - An important feature which Yahoo Kimo IME supports. --- Source/Engine/ControllerModules/KeyHandler.mm | 24 +++++++++++++++---- .../ControllerModules/KeyHandlerInput.swift | 4 ++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Source/Engine/ControllerModules/KeyHandler.mm b/Source/Engine/ControllerModules/KeyHandler.mm index 236e869e9..f1fd0d8ef 100644 --- a/Source/Engine/ControllerModules/KeyHandler.mm +++ b/Source/Engine/ControllerModules/KeyHandler.mm @@ -220,7 +220,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it - BOOL isFunctionKey = ([input isCommandHold] || [input isControlHold] || [input isOptionHold] || [input isNumericPad]); + BOOL isFunctionKey = ([input isCommandHold] || [input isOptionHold] || [input isNumericPad]) || [input isControlHotKey]; if (![state isKindOfClass:[InputStateNotEmpty class]] && isFunctionKey) { return NO; } @@ -282,7 +282,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // MARK: Handle BPMF Keys // see if it's valid BPMF reading - if (_bpmfReadingBuffer->isValidKey((char) charCode)) { + if (![input isControlHold] && _bpmfReadingBuffer->isValidKey((char) charCode)) { _bpmfReadingBuffer->combineKey((char) charCode); // if we have a tone marker, we have to insert the reading to the @@ -457,8 +457,15 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // MARK: Punctuation // if nothing is matched, see if it's a punctuation key for current layout. - string layout = [self _currentLayout]; - string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_") : string("_punctuation_"); + string punctuationNamePrefix; + if ([input isControlHold]) { + punctuationNamePrefix = string("_ctrl_punctuation_"); + } else if (Preferences.halfWidthPunctuationEnabled) { + punctuationNamePrefix = string("_half_punctuation_"); + } else { + punctuationNamePrefix = string("_punctuation_"); + } + string layout = [self _currentLayout]; string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode); if ([self _handlePunctuation:customPunctuation state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) { return YES; @@ -998,7 +1005,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if (Preferences.useWinNT351BPMF) { string layout = [self _currentLayout]; - string punctuationNamePrefix = Preferences.halfWidthPunctuationEnabled ? string("_half_punctuation_") : string("_punctuation_"); + string punctuationNamePrefix; + if ([input isControlHold]) { + punctuationNamePrefix = string("_ctrl_punctuation_"); + } else if (Preferences.halfWidthPunctuationEnabled) { + punctuationNamePrefix = string("_half_punctuation_"); + } else { + punctuationNamePrefix = string("_punctuation_"); + } string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode); string punctuation = punctuationNamePrefix + string(1, (char) charCode); diff --git a/Source/Engine/ControllerModules/KeyHandlerInput.swift b/Source/Engine/ControllerModules/KeyHandlerInput.swift index 6946e3e7a..131327b1c 100644 --- a/Source/Engine/ControllerModules/KeyHandlerInput.swift +++ b/Source/Engine/ControllerModules/KeyHandlerInput.swift @@ -84,6 +84,10 @@ class KeyHandlerInput: NSObject { flags.contains([.control]) } + @objc var isControlHotKey: Bool { + flags.contains([.control]) && inputText?.first?.isLetter ?? false + } + @objc var isOptionHold: Bool { flags.contains([.option]) } -- Gitee From 3e561d215ced846eb2e03041ef56a33c73811264 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 1 Feb 2022 14:35:23 +0800 Subject: [PATCH 145/163] Zonble: UserPhrases // UTF8 Surrogate Pair Fix - phase 1 - This already fixed the issue, but we need another commit to prevent disastrous results by similar issues. --- .../Engine/ControllerModules/InputState.swift | 425 +++++++++++------- Source/Engine/ControllerModules/KeyHandler.mm | 98 ++-- Source/InputMethodController.mm | 8 + 3 files changed, 317 insertions(+), 214 deletions(-) diff --git a/Source/Engine/ControllerModules/InputState.swift b/Source/Engine/ControllerModules/InputState.swift index 81d0af544..835c4a4c7 100644 --- a/Source/Engine/ControllerModules/InputState.swift +++ b/Source/Engine/ControllerModules/InputState.swift @@ -40,180 +40,267 @@ import Cocoa /// - Choosing Candidate: The candidate window is open to let the user to choose /// one among the candidates. class InputState: NSObject { -} - -/// Represents that the input controller is deactivated. -class InputStateDeactivated: InputState { - override var description: String { - "" - } -} -/// Represents that the composing buffer is empty. -class InputStateEmpty: InputState { - @objc var composingBuffer: String { - "" - } -} + /// Represents that the input controller is deactivated. + @objc (InputStateDeactivated) + class Deactivated: InputState { + override var description: String { + "" + } + } + + /// Represents that the composing buffer is empty. + @objc (InputStateEmpty) + class Empty: InputState { + @objc var composingBuffer: String { + "" + } + + override var description: String { + "" + } + } + + /// Represents that the composing buffer is empty. + @objc (InputStateEmptyIgnoringPreviousState) + class EmptyIgnoringPreviousState: InputState { + @objc var composingBuffer: String { + "" + } + override var description: String { + "" + } + } + + /// Represents that the input controller is committing text into client app. + @objc (InputStateCommitting) + class Committing: InputState { + @objc private(set) var poppedText: String = "" + + @objc convenience init(poppedText: String) { + self.init() + self.poppedText = poppedText + } + + override var description: String { + "" + } + } + /// Represents that the composing buffer is not empty. + @objc (InputStateNotEmpty) + class NotEmpty: InputState { + @objc private(set) var composingBuffer: String + @objc private(set) var cursorIndex: UInt + @objc private(set) var phrases: [InputPhrase] + + @objc init(composingBuffer: String, cursorIndex: UInt, phrases: [InputPhrase]) { + self.composingBuffer = composingBuffer + self.cursorIndex = cursorIndex + self.phrases = phrases + } + + override var description: String { + "" + } + } + + /// Represents that the user is inputting text. + @objc (InputStateInputting) + class Inputting: NotEmpty { + @objc var poppedText: String = "" + + @objc override init(composingBuffer: String, cursorIndex: UInt, phrases: [InputPhrase]) { + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex, phrases: phrases) + } + + @objc var attributedString: NSAttributedString { + let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0 + ]) + return attributedSting + } + + override var description: String { + ", poppedText:\(poppedText)>" + } + } + + private let kMinMarkRangeLength = 2 + private let kMaxMarkRangeLength = 6 + + /// Represents that the user is marking a range in the composing buffer. + @objc (InputStateMarking) + class Marking: NotEmpty { + + @objc private(set) var markerIndex: UInt + @objc private(set) var markedRange: NSRange + @objc var tooltip: String { + + if Preferences.phraseReplacementEnabled { + return NSLocalizedString("⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "") + } + + if markedRange.length == 0 { + return "" + } + + let text = (composingBuffer as NSString).substring(with: markedRange) + if markedRange.length < kMinMarkRangeLength { + return String(format: NSLocalizedString("\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text) + } else if (markedRange.length > kMaxMarkRangeLength) { + return String(format: NSLocalizedString("\"%@\" length should ≤ %d for a user phrase.", comment: ""), text, kMaxMarkRangeLength) + } + return String(format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: ""), text) + } + + @objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, phrases: [InputPhrase]) { + self.markerIndex = markerIndex + let begin = min(cursorIndex, markerIndex) + let end = max(cursorIndex, markerIndex) + markedRange = NSMakeRange(Int(begin), Int(end - begin)) + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex, phrases: phrases) + } + + @objc var attributedString: NSAttributedString { + let attributedSting = NSMutableAttributedString(string: composingBuffer) + let end = markedRange.location + markedRange.length + + attributedSting.setAttributes([ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0 + ], range: NSRange(location: 0, length: markedRange.location)) + attributedSting.setAttributes([ + .underlineStyle: NSUnderlineStyle.thick.rawValue, + .markedClauseSegment: 1 + ], range: markedRange) + attributedSting.setAttributes([ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 2 + ], range: NSRange(location: end, + length: (composingBuffer as NSString).length - end)) + return attributedSting + } + + override var description: String { + "" + } + + @objc func convertToInputting() -> Inputting { + let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex, phrases: phrases) + return state + } + + @objc var validToWrite: Bool { + markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength + } + + @objc var userPhrase: String { + let text = (composingBuffer as NSString).substring(with: markedRange) + let end = markedRange.location + markedRange.length + var selectedPhrases = [InputPhrase]() + var length = 0 + for component in self.phrases { + if length >= end { + break + } + if length >= markedRange.location { + selectedPhrases.append(component) + } + length += (component.text as NSString).length + } + + let readings = selectedPhrases.map { $0.reading } + let joined = readings.joined(separator: "-") + return "\(text) \(joined)" + } + } + + /// Represents that the user is choosing in a candidates list. + @objc (InputStateChoosingCandidate) + class ChoosingCandidate: NotEmpty { + @objc private(set) var candidates: [String] + @objc private(set) var useVerticalMode: Bool + + @objc init(composingBuffer: String, cursorIndex: UInt, candidates: [String], phrases: [InputPhrase], useVerticalMode: Bool) { + self.candidates = candidates + self.useVerticalMode = useVerticalMode + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex, phrases: phrases) + } + + @objc var attributedString: NSAttributedString { + let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .markedClauseSegment: 0 + ]) + return attributedSting + } + + override var description: String { + "" + } + } + + /// Represents that the user is choosing in a candidates list + /// in the associated phrases mode. + @objc (InputStateAssociatedPhrases) + class AssociatedPhrases: InputState { + @objc private(set) var candidates: [String] = [] + @objc private(set) var useVerticalMode: Bool = false + @objc init(candidates: [String], useVerticalMode: Bool) { + self.candidates = candidates + self.useVerticalMode = useVerticalMode + super.init() + } + + override var description: String { + "" + } + } -/// Represents that the composing buffer is empty. -class InputStateEmptyIgnoringPreviousState: InputState { - @objc var composingBuffer: String { - "" - } } -/// Represents that the input controller is committing text into client app. -class InputStateCommitting: InputState { - @objc private(set) var poppedText: String = "" - - @objc convenience init(poppedText: String) { - self.init() - self.poppedText = poppedText - } - - override var description: String { - "" - } -} - -/// Represents that the composing buffer is not empty. -class InputStateNotEmpty: InputState { - @objc private(set) var composingBuffer: String = "" - @objc private(set) var cursorIndex: UInt = 0 - - @objc init(composingBuffer: String, cursorIndex: UInt) { - self.composingBuffer = composingBuffer - self.cursorIndex = cursorIndex - } - - override var description: String { - "" - } -} - -/// Represents that the user is inputting text. -class InputStateInputting: InputStateNotEmpty { - @objc var bpmfReading: String = "" - @objc var bpmfReadingCursorIndex: UInt8 = 0 - @objc var poppedText: String = "" - - @objc override init(composingBuffer: String, cursorIndex: UInt) { - super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) - } - - @objc var attributedString: NSAttributedString { - let attributedSting = NSAttributedString(string: composingBuffer, attributes: [ - .underlineStyle: NSUnderlineStyle.single.rawValue, - .markedClauseSegment: 0 - ]) - return attributedSting - } - - override var description: String { - "" - } -} +class InputPhrase: NSObject { + @objc private (set) var text: String + @objc private (set) var reading: String -private let kMinMarkRangeLength = 2 -private let kMaxMarkRangeLength = Preferences.maxCandidateLength - -/// Represents that the user is marking a range in the composing buffer. -class InputStateMarking: InputStateNotEmpty { - @objc private(set) var markerIndex: UInt - @objc private(set) var markedRange: NSRange - @objc var tooltip: String { - - if Preferences.phraseReplacementEnabled { - return NSLocalizedString("⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "") - } - - if markedRange.length == 0 { - return "" - } - - let text = (composingBuffer as NSString).substring(with: markedRange) - if markedRange.length < kMinMarkRangeLength { - return String(format: NSLocalizedString("\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text) - } else if (markedRange.length > kMaxMarkRangeLength) { - return String(format: NSLocalizedString("\"%@\" length should ≤ %d for a user phrase.", comment: ""), text, kMaxMarkRangeLength) - } - return String(format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: ""), text) - } - - @objc private(set) var readings: [String] = [] - - @objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) { - self.markerIndex = markerIndex - let begin = min(cursorIndex, markerIndex) - let end = max(cursorIndex, markerIndex) - markedRange = NSMakeRange(Int(begin), Int(end - begin)) - self.readings = readings - super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) - } - - @objc var attributedString: NSAttributedString { - let attributedSting = NSMutableAttributedString(string: composingBuffer) - let end = markedRange.location + markedRange.length - - attributedSting.setAttributes([ - .underlineStyle: NSUnderlineStyle.single.rawValue, - .markedClauseSegment: 0 - ], range: NSRange(location: 0, length: markedRange.location)) - attributedSting.setAttributes([ - .underlineStyle: NSUnderlineStyle.thick.rawValue, - .markedClauseSegment: 1 - ], range: markedRange) - attributedSting.setAttributes([ - .underlineStyle: NSUnderlineStyle.single.rawValue, - .markedClauseSegment: 2 - ], range: NSRange(location: end, - length: composingBuffer.count - end)) - return attributedSting - } - - override var description: String { - "" - } - - @objc func convertToInputting() -> InputStateInputting { - let state = InputStateInputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) - return state - } - - @objc var validToWrite: Bool { - markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength - } - - @objc var userPhrase: String { - let text = (composingBuffer as NSString).substring(with: markedRange) - let end = markedRange.location + markedRange.length - let readings = readings[markedRange.location.." - } +class StringUtils: NSObject { + + static func convertToCharIndex(from utf16Index: Int, in string: String) -> Int { + var length = 0 + for (i, c) in string.enumerated() { + if length >= utf16Index { + return i + } + length += c.utf16.count + } + return string.count + } + + @objc (nextUtf16PositionForIndex:in:) + static func nextUtf16Position(for index: Int, in string: String) -> Int { + var index = convertToCharIndex(from: index, in: string) + if index < string.count { + index += 1 + } + let count = string[.. Int { + var index = convertToCharIndex(from: index, in: string) + if index > 0 { + index -= 1 + } + let count = string[..setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); - _languageModel->setCNSEnabled(Preferences.cns11643Enabled); - _userOverrideModel = [LanguageModelManager userOverrideModelCHT]; - - _builder = new BlockReadingBuilder(_languageModel); + // create the lattice builder + _languageModel = [LanguageModelManager languageModelCoreCHT]; + _languageModel->setPhraseReplacementEnabled(Preferences.phraseReplacementEnabled); + _languageModel->setCNSEnabled(Preferences.cns11643Enabled); + _userOverrideModel = [LanguageModelManager userOverrideModelCHT]; + + _builder = new BlockReadingBuilder(_languageModel); // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -220,7 +220,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } // if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it - BOOL isFunctionKey = ([input isCommandHold] || [input isOptionHold] || [input isNumericPad]) || [input isControlHotKey]; + BOOL isFunctionKey = ([input isCommandHold] || [input isOptionHold] || [input isNumericPad]) || [input isControlHotKey]; if (![state isKindOfClass:[InputStateNotEmpty class]] && isFunctionKey) { return NO; } @@ -282,7 +282,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // MARK: Handle BPMF Keys // see if it's valid BPMF reading - if (![input isControlHold] && _bpmfReadingBuffer->isValidKey((char) charCode)) { + if (![input isControlHold] && _bpmfReadingBuffer->isValidKey((char) charCode)) { _bpmfReadingBuffer->combineKey((char) charCode); // if we have a tone marker, we have to insert the reading to the @@ -364,14 +364,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // if the spacebar is NOT set to be a selection key if ([input isShiftHold] || !Preferences.chooseCandidateUsingSpace) { if (_builder->cursorIndex() >= _builder->length()) { - if ([state isKindOfClass:[InputStateNotEmpty class]]) { - NSString *composingBuffer = [(InputStateNotEmpty *)state composingBuffer]; - if ([composingBuffer length]) { - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; - stateCallback(committing); - } - } - [self clear]; + if ([state isKindOfClass:[InputStateNotEmpty class]]) { + NSString *composingBuffer = [(InputStateNotEmpty *)state composingBuffer]; + if ([composingBuffer length]) { + InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; + stateCallback(committing); + } + } + [self clear]; InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:@" "]; stateCallback(committing); InputStateEmpty *empty = [[InputStateEmpty alloc] init]; @@ -457,15 +457,15 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // MARK: Punctuation // if nothing is matched, see if it's a punctuation key for current layout. - string punctuationNamePrefix; - if ([input isControlHold]) { - punctuationNamePrefix = string("_ctrl_punctuation_"); - } else if (Preferences.halfWidthPunctuationEnabled) { - punctuationNamePrefix = string("_half_punctuation_"); - } else { - punctuationNamePrefix = string("_punctuation_"); - } - string layout = [self _currentLayout]; + string punctuationNamePrefix; + if ([input isControlHold]) { + punctuationNamePrefix = string("_ctrl_punctuation_"); + } else if (Preferences.halfWidthPunctuationEnabled) { + punctuationNamePrefix = string("_half_punctuation_"); + } else { + punctuationNamePrefix = string("_punctuation_"); + } + string layout = [self _currentLayout]; string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode); if ([self _handlePunctuation:customPunctuation state:state usingVerticalMode:input.useVerticalMode stateCallback:stateCallback errorCallback:errorCallback]) { return YES; @@ -543,8 +543,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isShiftHold]) { // Shift + left - if (_builder->cursorIndex() > 0) { - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:currentState.cursorIndex - 1 readings: [self _currentReadings]]; + if (currentState.cursorIndex > 0) { + NSInteger previousPosition = [StringUtils previousUtf16PositionForIndex:currentState.cursorIndex in:currentState.composingBuffer]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:previousPosition phrases:currentState.phrases]; stateCallback(marking); } else { errorCallback(); @@ -579,8 +580,9 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if ([input isShiftHold]) { // Shift + Right - if (_builder->cursorIndex() < _builder->length()) { - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:currentState.cursorIndex + 1 readings: [self _currentReadings]]; + if (currentState.cursorIndex < currentState.composingBuffer.length) { + NSInteger nextPosition = [StringUtils nextUtf16PositionForIndex:currentState.cursorIndex in:currentState.composingBuffer]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:nextPosition phrases:currentState.phrases]; stateCallback(marking); } else { errorCallback(); @@ -806,8 +808,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; && ([input isShiftHold])) { NSUInteger index = state.markerIndex; if (index > 0) { - index -= 1; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings]; + index = [StringUtils previousUtf16PositionForIndex:index in:state.composingBuffer]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index phrases:state.phrases]; stateCallback(marking); } else { errorCallback(); @@ -821,8 +823,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; && ([input isShiftHold])) { NSUInteger index = state.markerIndex; if (index < state.composingBuffer.length) { - index += 1; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings]; + index = [StringUtils nextUtf16PositionForIndex:index in:state.composingBuffer]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index phrases:state.phrases]; stateCallback(marking); } else { errorCallback(); @@ -844,7 +846,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; UniChar charCode = input.charCode; VTCandidateController *gCurrentCandidateController = [self.delegate candidateControllerForKeyHandler:self]; - BOOL cancelCandidateKey = (charCode == 27) || (charCode == 8) || [input isDelete]; + BOOL cancelCandidateKey = (charCode == 27) || (charCode == 8) || [input isDelete]; if (cancelCandidateKey) { if (Preferences.useWinNT351BPMF) { @@ -1005,14 +1007,14 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; if (Preferences.useWinNT351BPMF) { string layout = [self _currentLayout]; - string punctuationNamePrefix; - if ([input isControlHold]) { - punctuationNamePrefix = string("_ctrl_punctuation_"); - } else if (Preferences.halfWidthPunctuationEnabled) { - punctuationNamePrefix = string("_half_punctuation_"); - } else { - punctuationNamePrefix = string("_punctuation_"); - } + string punctuationNamePrefix; + if ([input isControlHold]) { + punctuationNamePrefix = string("_ctrl_punctuation_"); + } else if (Preferences.halfWidthPunctuationEnabled) { + punctuationNamePrefix = string("_half_punctuation_"); + } else { + punctuationNamePrefix = string("_punctuation_"); + } string customPunctuation = punctuationNamePrefix + layout + string(1, (char) charCode); string punctuation = punctuationNamePrefix + string(1, (char) charCode); @@ -1056,6 +1058,8 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; size_t readingCursorIndex = 0; size_t builderCursorIndex = _builder->cursorIndex(); + NSMutableArray *phrases = [[NSMutableArray alloc] init]; + // we must do some Unicode codepoint counting to find the actual cursor location for the client // i.e. we need to take UTF-16 into consideration, for which a surrogate pair takes 2 UniChars // locations @@ -1068,6 +1072,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSString *valueString = [NSString stringWithUTF8String:nodeStr.c_str()]; [composingBuffer appendString:valueString]; + NSString *readingString = [NSString stringWithUTF8String:(*wi).node->currentKeyValue().key.c_str()]; + InputPhrase *phrase = [[InputPhrase alloc] initWithText:valueString reading:readingString]; + [phrases addObject:phrase]; + // this re-aligns the cursor index in the composed string // (the actual cursor on the screen) with the builder's logical // cursor (reading) cursor; each built node has a "spanning length" @@ -1096,7 +1104,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; NSInteger cursorIndex = composedStringCursorIndex + [reading length]; - InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText cursorIndex:cursorIndex]; + InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText cursorIndex:cursorIndex phrases:phrases]; return newState; } @@ -1166,7 +1174,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } } - InputStateChoosingCandidate *state = [[InputStateChoosingCandidate alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex candidates:candidatesArray useVerticalMode:useVerticalMode]; + InputStateChoosingCandidate *state = [[InputStateChoosingCandidate alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex candidates:candidatesArray phrases:currentState.phrases useVerticalMode:useVerticalMode]; return state; } diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 53bb1143e..8eb8fd6b9 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -443,6 +443,10 @@ static inline NSString *LocalizationNotNeeded(NSString *s) { // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch @try { [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; + if ((lineHeightRect.origin.x == 0) && (lineHeightRect.origin.y == 0) && (cursor > 0)) { + cursor -= 1; // Zonble's UPR fix: "Corrects the selection range while using Shift + Arrow keys to add new phrases." + [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; + } } @catch (NSException *exception) { NSLog(@"lineHeightRectangle %@", exception); @@ -688,6 +692,10 @@ static inline NSString *LocalizationNotNeeded(NSString *s) { // some apps (e.g. Twitter for Mac's search bar) handle this call incorrectly, hence the try-catch @try { [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; + if ((lineHeightRect.origin.x == 0) && (lineHeightRect.origin.y == 0) && (cursor > 0)) { + cursor -= 1; // Zonble's UPR fix: "Corrects the selection range while using Shift + Arrow keys to add new phrases." + [client attributesForCharacterIndex:cursor lineHeightRectangle:&lineHeightRect]; + } } @catch (NSException *exception) { NSLog(@"%@", exception); -- Gitee From 2d444d174c329059b3ad3918658270fcda72a5c8 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 1 Feb 2022 15:05:32 +0800 Subject: [PATCH 146/163] Zonble: UserPhrases // UTF8 Surrogate Pair Fix - phase 2 - Add a feature to block users from adding user phrases when an unhandlable char appeared in the selection. --- .../Engine/ControllerModules/InputState.swift | 59 +++++++++---------- Source/Engine/ControllerModules/KeyHandler.mm | 12 ++-- Source/en.lproj/Localizable.strings | 1 + Source/ja.lproj/Localizable.strings | 1 + Source/zh-Hans.lproj/Localizable.strings | 1 + Source/zh-Hant.lproj/Localizable.strings | 1 + 6 files changed, 38 insertions(+), 37 deletions(-) diff --git a/Source/Engine/ControllerModules/InputState.swift b/Source/Engine/ControllerModules/InputState.swift index 835c4a4c7..23e914348 100644 --- a/Source/Engine/ControllerModules/InputState.swift +++ b/Source/Engine/ControllerModules/InputState.swift @@ -91,16 +91,14 @@ class InputState: NSObject { class NotEmpty: InputState { @objc private(set) var composingBuffer: String @objc private(set) var cursorIndex: UInt - @objc private(set) var phrases: [InputPhrase] - @objc init(composingBuffer: String, cursorIndex: UInt, phrases: [InputPhrase]) { + @objc init(composingBuffer: String, cursorIndex: UInt) { self.composingBuffer = composingBuffer self.cursorIndex = cursorIndex - self.phrases = phrases } override var description: String { - "" + "" } } @@ -109,8 +107,8 @@ class InputState: NSObject { class Inputting: NotEmpty { @objc var poppedText: String = "" - @objc override init(composingBuffer: String, cursorIndex: UInt, phrases: [InputPhrase]) { - super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex, phrases: phrases) + @objc override init(composingBuffer: String, cursorIndex: UInt) { + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) } @objc var attributedString: NSAttributedString { @@ -122,7 +120,7 @@ class InputState: NSObject { } override var description: String { - ", poppedText:\(poppedText)>" + ", poppedText:\(poppedText)>" } } @@ -136,7 +134,11 @@ class InputState: NSObject { @objc private(set) var markerIndex: UInt @objc private(set) var markedRange: NSRange @objc var tooltip: String { - + + if composingBuffer.count != readings.count { + return NSLocalizedString("⚠︎ Unhandlable char selected for user phrases.", comment: "") + } + if Preferences.phraseReplacementEnabled { return NSLocalizedString("⚠︎ Phrase replacement mode enabled, interfering user phrase entry.", comment: "") } @@ -144,7 +146,7 @@ class InputState: NSObject { if markedRange.length == 0 { return "" } - + let text = (composingBuffer as NSString).substring(with: markedRange) if markedRange.length < kMinMarkRangeLength { return String(format: NSLocalizedString("\"%@\" length must ≥ 2 for a user phrase.", comment: ""), text) @@ -154,12 +156,15 @@ class InputState: NSObject { return String(format: NSLocalizedString("\"%@\" selected. ENTER to add user phrase.", comment: ""), text) } - @objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, phrases: [InputPhrase]) { + @objc private(set) var readings: [String] + + @objc init(composingBuffer: String, cursorIndex: UInt, markerIndex: UInt, readings: [String]) { self.markerIndex = markerIndex let begin = min(cursorIndex, markerIndex) let end = max(cursorIndex, markerIndex) markedRange = NSMakeRange(Int(begin), Int(end - begin)) - super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex, phrases: phrases) + self.readings = readings + super.init(composingBuffer: composingBuffer, cursorIndex: cursorIndex) } @objc var attributedString: NSAttributedString { @@ -183,34 +188,26 @@ class InputState: NSObject { } override var description: String { - "" + "" } @objc func convertToInputting() -> Inputting { - let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex, phrases: phrases) + let state = Inputting(composingBuffer: composingBuffer, cursorIndex: cursorIndex) return state } @objc var validToWrite: Bool { - markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength + if composingBuffer.count != readings.count { + return false + } + return markedRange.length >= kMinMarkRangeLength && markedRange.length <= kMaxMarkRangeLength } @objc var userPhrase: String { let text = (composingBuffer as NSString).substring(with: markedRange) - let end = markedRange.location + markedRange.length - var selectedPhrases = [InputPhrase]() - var length = 0 - for component in self.phrases { - if length >= end { - break - } - if length >= markedRange.location { - selectedPhrases.append(component) - } - length += (component.text as NSString).length - } - - let readings = selectedPhrases.map { $0.reading } + let exactBegin = StringUtils.convertToCharIndex(from: markedRange.location, in: composingBuffer) + let exactEnd = StringUtils.convertToCharIndex(from: markedRange.location + markedRange.length, in: composingBuffer) + let readings = readings[exactBegin.." + "" } } diff --git a/Source/Engine/ControllerModules/KeyHandler.mm b/Source/Engine/ControllerModules/KeyHandler.mm index ee51842ad..fc3ba67e7 100644 --- a/Source/Engine/ControllerModules/KeyHandler.mm +++ b/Source/Engine/ControllerModules/KeyHandler.mm @@ -545,7 +545,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // Shift + left if (currentState.cursorIndex > 0) { NSInteger previousPosition = [StringUtils previousUtf16PositionForIndex:currentState.cursorIndex in:currentState.composingBuffer]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:previousPosition phrases:currentState.phrases]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:previousPosition readings:[self _currentReadings]]; stateCallback(marking); } else { errorCallback(); @@ -582,7 +582,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // Shift + Right if (currentState.cursorIndex < currentState.composingBuffer.length) { NSInteger nextPosition = [StringUtils nextUtf16PositionForIndex:currentState.cursorIndex in:currentState.composingBuffer]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:nextPosition phrases:currentState.phrases]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex markerIndex:nextPosition readings:[self _currentReadings]]; stateCallback(marking); } else { errorCallback(); @@ -809,7 +809,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSUInteger index = state.markerIndex; if (index > 0) { index = [StringUtils previousUtf16PositionForIndex:index in:state.composingBuffer]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index phrases:state.phrases]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings]; stateCallback(marking); } else { errorCallback(); @@ -824,7 +824,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSUInteger index = state.markerIndex; if (index < state.composingBuffer.length) { index = [StringUtils nextUtf16PositionForIndex:index in:state.composingBuffer]; - InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index phrases:state.phrases]; + InputStateMarking *marking = [[InputStateMarking alloc] initWithComposingBuffer:state.composingBuffer cursorIndex:state.cursorIndex markerIndex:index readings:state.readings]; stateCallback(marking); } else { errorCallback(); @@ -1104,7 +1104,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; NSString *composedText = [head stringByAppendingString:[reading stringByAppendingString:tail]]; NSInteger cursorIndex = composedStringCursorIndex + [reading length]; - InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText cursorIndex:cursorIndex phrases:phrases]; + InputStateInputting *newState = [[InputStateInputting alloc] initWithComposingBuffer:composedText cursorIndex:cursorIndex]; return newState; } @@ -1174,7 +1174,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } } - InputStateChoosingCandidate *state = [[InputStateChoosingCandidate alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex candidates:candidatesArray phrases:currentState.phrases useVerticalMode:useVerticalMode]; + InputStateChoosingCandidate *state = [[InputStateChoosingCandidate alloc] initWithComposingBuffer:currentState.composingBuffer cursorIndex:currentState.cursorIndex candidates:candidatesArray useVerticalMode:useVerticalMode]; return state; } diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 26ba4bb10..8b897e3bc 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -34,6 +34,7 @@ "Please specify at least 4 candidate keys." = "Please specify at least 4 candidate keys."; "Maximum 15 candidate keys allowed." = "Maximum 15 candidate keys allowed."; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ Phrase replacement mode enabled, interfering user phrase entry."; +"⚠︎ Unhandlable char selected for user phrases." = "⚠︎ Unhandlable char selected for user phrases."; "NT351 BPMF EMU" = "NT351 Per-Char Select Mode"; "CNS11643 Mode" = "CNS11643 Mode"; "Reboot vChewing…" = "Reboot vChewing…"; diff --git a/Source/ja.lproj/Localizable.strings b/Source/ja.lproj/Localizable.strings index 73d5cbee0..0224e8028 100644 --- a/Source/ja.lproj/Localizable.strings +++ b/Source/ja.lproj/Localizable.strings @@ -34,6 +34,7 @@ "Please specify at least 4 candidate keys." = "言選り用キー陣列に少なくとも4つのキーをご登録ください。"; "Maximum 15 candidate keys allowed." = "言選り用キー陣列には最多15つキー登録できます。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 言葉置換機能稼働中、新添付言葉にも影響。"; +"⚠︎ Unhandlable char selected for user phrases." = "⚠︎ ユーザー辞書の対処できない文字は選択されています。"; "NT351 BPMF EMU" = "全候補入力モード"; "CNS11643 Mode" = "全字庫モード"; "Reboot vChewing…" = "入力アプリ再起動…"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 7a3e14c11..5d6fb0693 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -34,6 +34,7 @@ "Please specify at least 4 candidate keys." = "请至少指定四个选字键。"; "Maximum 15 candidate keys allowed." = "选字键最多只能指定十五个。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 语汇置换功能已启用,会波及语汇自订。"; +"⚠︎ Unhandlable char selected for user phrases." = "⚠︎ 已选中无法处理的字元,无法加入自订语汇。"; "NT351 BPMF EMU" = "模拟逐字选字输入"; "CNS11643 Mode" = "全字库模式"; "Reboot vChewing…" = "重新启动输入法…"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 1066c72fe..b0bc929f1 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -34,6 +34,7 @@ "Please specify at least 4 candidate keys." = "請至少指定四個選字鍵。"; "Maximum 15 candidate keys allowed." = "選字鍵最多只能指定十五個。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 語彙置換功能已啟用,會波及語彙自訂。"; +"⚠︎ Unhandlable char selected for user phrases." = "⚠︎ 已選中無法處理的字元,無法加入自訂語彙。"; "NT351 BPMF EMU" = "模擬逐字選字輸入"; "CNS11643 Mode" = "全字庫模式"; "Reboot vChewing…" = "重新啟動輸入法…"; -- Gitee From c4958799adc87df26869db3ad701e4a27c32913f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 1 Feb 2022 20:06:23 +0800 Subject: [PATCH 147/163] Shiki: Installer // Tweaks in AlertPanel and Japanese Localization. - The word "Warning" requires careful translation in Japanese since native Japanese users are sensitive to the choice of words. --- Source/Installer/AppDelegate.swift | 21 +++++++++++++------ Source/Installer/ja.lproj/Localizable.strings | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Source/Installer/AppDelegate.swift b/Source/Installer/AppDelegate.swift index f4b08791f..ed2e0fa63 100644 --- a/Source/Installer/AppDelegate.swift +++ b/Source/Installer/AppDelegate.swift @@ -228,18 +228,27 @@ class AppDelegate: NSWindowController, NSApplicationDelegate { NSLog("Failed to enable input method: \(imeIdentifier)"); } } - + + // Alert Panel + let ntfPostInstall = NSAlert() if warning { - runAlertPanel(title: NSLocalizedString("Attention", comment: ""), message: NSLocalizedString("vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", comment: ""), buttonTitle: NSLocalizedString("OK", comment: "")) + ntfPostInstall.messageText = NSLocalizedString("Attention", comment: "") + ntfPostInstall.informativeText = NSLocalizedString("vChewing is upgraded, but please log out or reboot for the new version to be fully functional.", comment: "") + ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) } else { if !mainInputSourceEnabled && !isMacOS12OrAbove { - runAlertPanel(title: NSLocalizedString("Warning", comment: ""), message: NSLocalizedString("Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", comment: ""), buttonTitle: NSLocalizedString("Continue", comment: "")) + ntfPostInstall.messageText = NSLocalizedString("Warning", comment: "") + ntfPostInstall.informativeText = NSLocalizedString("Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources.", comment: "") + ntfPostInstall.addButton(withTitle: NSLocalizedString("Continue", comment: "")) } else { - runAlertPanel(title: NSLocalizedString("Installation Successful", comment: ""), message: NSLocalizedString("vChewing is ready to use.", comment: ""), buttonTitle: NSLocalizedString("OK", comment: "")) + ntfPostInstall.messageText = NSLocalizedString("Installation Successful", comment: "") + ntfPostInstall.informativeText = NSLocalizedString("vChewing is ready to use.", comment: "") + ntfPostInstall.addButton(withTitle: NSLocalizedString("OK", comment: "")) } } - - endAppWithDelay() + ntfPostInstall.beginSheetModal(for: window!) { response in + self.endAppWithDelay() + } } func endAppWithDelay() { diff --git a/Source/Installer/ja.lproj/Localizable.strings b/Source/Installer/ja.lproj/Localizable.strings index 44d4c467e..bbb3011e0 100644 --- a/Source/Installer/ja.lproj/Localizable.strings +++ b/Source/Installer/ja.lproj/Localizable.strings @@ -13,6 +13,6 @@ "Abort" = "中止"; "Cannot register input source %@ at %@." = "「%2$@」で入力アプリ「\"%1$@\"」の実装は失敗しました。"; "Cannot find input source %@ after registration." = "登録済みですが「%@」は見つけませんでした。"; -"Warning" = "警告"; +"Warning" = "お知らせ"; "Input method may not be fully enabled. Please enable it through System Preferences > Keyboard > Input Sources." = "入力アプリの自動起動はうまく出来なかったかもしれません。ご自分で「システム環境設定→キーボード→入力ソース」で起動してください。"; "Continue" = "続行"; -- Gitee From b8be76bc071caa9400ea77b943792941a96e05ed Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Tue, 1 Feb 2022 21:14:18 +0800 Subject: [PATCH 148/163] Fix wrong space chars in CNS11643 database. -- Gitee From 5465deba7cb41aecd14a96b5ea23b3840de85fd1 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 2 Feb 2022 01:23:09 +0800 Subject: [PATCH 149/163] AppDelegate // Dealloc the fsStreamHelper when App terminates. --- Source/AppDelegate.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/AppDelegate.swift b/Source/AppDelegate.swift index 65552c772..02f4d82af 100644 --- a/Source/AppDelegate.swift +++ b/Source/AppDelegate.swift @@ -137,7 +137,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle } } } - @IBOutlet weak var window: NSWindow? private var preferencesWindowController: PreferencesWindowController? @@ -152,6 +151,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle aboutWindowController = nil checkTask = nil updateNextStepURL = nil + fsStreamHelper.stop() + fsStreamHelper.delegate = nil } func applicationDidFinishLaunching(_ notification: Notification) { -- Gitee From 3cdc7486631c37e38f64a245d1b1a81c43f90bc9 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 2 Feb 2022 11:58:26 +0800 Subject: [PATCH 150/163] Zonble: KeyHandler // Stop misidentifying reserved keys as bpmf keys. --- Source/Engine/ControllerModules/KeyHandler.mm | 3 ++- Source/Engine/ControllerModules/KeyHandlerInput.swift | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/Engine/ControllerModules/KeyHandler.mm b/Source/Engine/ControllerModules/KeyHandler.mm index fc3ba67e7..515d19297 100644 --- a/Source/Engine/ControllerModules/KeyHandler.mm +++ b/Source/Engine/ControllerModules/KeyHandler.mm @@ -279,10 +279,11 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; } bool composeReading = false; + bool skipBpmfHandling = [input isReservedKey] || [input isControlHold]; // MARK: Handle BPMF Keys // see if it's valid BPMF reading - if (![input isControlHold] && _bpmfReadingBuffer->isValidKey((char) charCode)) { + if (!skipBpmfHandling && _bpmfReadingBuffer->isValidKey((char) charCode)) { _bpmfReadingBuffer->combineKey((char) charCode); // if we have a tone marker, we have to insert the reading to the diff --git a/Source/Engine/ControllerModules/KeyHandlerInput.swift b/Source/Engine/ControllerModules/KeyHandlerInput.swift index 131327b1c..ea2390f5f 100644 --- a/Source/Engine/ControllerModules/KeyHandlerInput.swift +++ b/Source/Engine/ControllerModules/KeyHandlerInput.swift @@ -72,6 +72,10 @@ class KeyHandlerInput: NSObject { super.init() } + override var description: String { + return "<\(super.description) inputText:\(String(describing: inputText)), charCode:\(charCode), keyCode:\(keyCode), flags:\(flags), cursorForwardKey:\(cursorForwardKey), cursorBackwardKey:\(cursorBackwardKey), extraChooseCandidateKey:\(extraChooseCandidateKey), absorbedArrowKey:\(absorbedArrowKey), verticalModeOnlyChooseCandidateKey:\(verticalModeOnlyChooseCandidateKey), emacsKey:\(emacsKey), useVerticalMode:\(useVerticalMode)>" + } + @objc var isShiftHold: Bool { flags.contains([.shift]) } @@ -100,6 +104,13 @@ class KeyHandlerInput: NSObject { flags.contains([.numericPad]) } + @objc var isReservedKey: Bool { + guard let code = KeyCode(rawValue: keyCode) else { + return false + } + return code.rawValue != KeyCode.none.rawValue + } + @objc var isEnter: Bool { KeyCode(rawValue: keyCode) == KeyCode.enter } -- Gitee From 40e28d8657666e73e11ee5a1885a350bf462eb94 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 2 Feb 2022 13:13:45 +0800 Subject: [PATCH 151/163] Shiki: KeyHandler // Also call candidate window by PgUp & PgDn. --- Source/Engine/ControllerModules/KeyHandler.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/ControllerModules/KeyHandler.mm b/Source/Engine/ControllerModules/KeyHandler.mm index 515d19297..1e0ef6af7 100644 --- a/Source/Engine/ControllerModules/KeyHandler.mm +++ b/Source/Engine/ControllerModules/KeyHandler.mm @@ -359,8 +359,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; // MARK: Space and Down // keyCode 125 = Down, charCode 32 = Space if (_bpmfReadingBuffer->isEmpty() && - [state isKindOfClass:[InputStateNotEmpty class]] && - ([input isExtraChooseCandidateKey] || charCode == 32 || (input.useVerticalMode && ([input isVerticalModeOnlyChooseCandidateKey])))) { + [state isKindOfClass:[InputStateNotEmpty class]] && + ([input isExtraChooseCandidateKey] || charCode == 32 + || [input isPageDown] || [input isPageUp] + || (input.useVerticalMode && ([input isVerticalModeOnlyChooseCandidateKey])))) { if (charCode == 32) { // if the spacebar is NOT set to be a selection key if ([input isShiftHold] || !Preferences.chooseCandidateUsingSpace) { -- Gitee From d3032053abccaced020c996cdff9e1c22abdfa25 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 2 Feb 2022 11:58:19 +0800 Subject: [PATCH 152/163] Lukhnos: Taiyan // Refactor for better thread safety - See UPR269. - A vChewing-Specific change, allowing the HYPY input mode to support the intonation key "1", is unavailable in this update. It will be brought back later if possible. --- .../3rdParty/OpenVanilla/Mandarin/Mandarin.hh | 991 +++++----- .../3rdParty/OpenVanilla/Mandarin/Mandarin.mm | 1608 ++++++++++------- 2 files changed, 1382 insertions(+), 1217 deletions(-) diff --git a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.hh b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.hh index 102c19efe..dc5d483ab 100644 --- a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.hh +++ b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.hh @@ -5,583 +5,500 @@ * All rights reserved. See "LICENSE.TXT" for details. */ -#ifndef Mandarin_h -#define Mandarin_h +#ifndef MANDARIN_H_ +#define MANDARIN_H_ #include +#include #include #include -#include namespace Taiyan { - namespace Mandarin { - using namespace std; +namespace Mandarin { + +class BopomofoSyllable { +public: + typedef uint16_t Component; + + explicit BopomofoSyllable(Component syllable = 0) : syllable_(syllable) {} + + BopomofoSyllable(const BopomofoSyllable&) = default; + BopomofoSyllable(BopomofoSyllable&& another) = default; + BopomofoSyllable& operator=(const BopomofoSyllable&) = default; + BopomofoSyllable& operator=(BopomofoSyllable&&) = default; + + // takes the ASCII-form, "v"-tolerant, Non-Continental-style Hanyu Pinyin (fong, pong, bong + // acceptable) + static const BopomofoSyllable FromHanyuPinyin(const std::string& str); + + // TO DO: Support accented vowels + const std::string HanyuPinyinString(bool includesTone, + bool useVForUUmlaut) const; + // const std::string HanyuPinyinString(bool includesTone, bool useVForUUmlaut, + // bool composeAccentedVowel) const; + + // PHT = Pai-hua-tsi + static const BopomofoSyllable FromPHT(const std::string& str); + const std::string PHTString(bool includesTone) const; + + static const BopomofoSyllable FromComposedString(const std::string& str); + const std::string composedString() const; + + void clear() { syllable_ = 0; } + + bool isEmpty() const { return !syllable_; } + + bool hasConsonant() const { return !!(syllable_ & ConsonantMask); } + + bool hasMiddleVowel() const { return !!(syllable_ & MiddleVowelMask); } + bool hasVowel() const { return !!(syllable_ & VowelMask); } + + bool hasToneMarker() const { return !!(syllable_ & ToneMarkerMask); } + + Component consonantComponent() const { return syllable_ & ConsonantMask; } + + Component middleVowelComponent() const { + return syllable_ & MiddleVowelMask; + } + + Component vowelComponent() const { return syllable_ & VowelMask; } + + Component toneMarkerComponent() const { return syllable_ & ToneMarkerMask; } + + bool operator==(const BopomofoSyllable& another) const { + return syllable_ == another.syllable_; + } + + bool operator!=(const BopomofoSyllable& another) const { + return syllable_ != another.syllable_; + } + + bool isOverlappingWith(const BopomofoSyllable& another) const { +#define IOW_SAND(mask) ((syllable_ & mask) && (another.syllable_ & mask)) + return IOW_SAND(ConsonantMask) || IOW_SAND(MiddleVowelMask) || + IOW_SAND(VowelMask) || IOW_SAND(ToneMarkerMask); +#undef IOW_SAND + } + + // consonants J, Q, X all require the existence of vowel I or UE + bool belongsToJQXClass() const { + Component consonant = syllable_ & ConsonantMask; + return (consonant == J || consonant == Q || consonant == X); + } + + // zi, ci, si, chi, chi, shi, ri + bool belongsToZCSRClass() const { + Component consonant = syllable_ & ConsonantMask; + return (consonant >= ZH && consonant <= S); + } + + Component maskType() const { + Component mask = 0; + mask |= (syllable_ & ConsonantMask) ? ConsonantMask : 0; + mask |= (syllable_ & MiddleVowelMask) ? MiddleVowelMask : 0; + mask |= (syllable_ & VowelMask) ? VowelMask : 0; + mask |= (syllable_ & ToneMarkerMask) ? ToneMarkerMask : 0; + return mask; + } + + const BopomofoSyllable operator+(const BopomofoSyllable& another) const { + Component newSyllable = syllable_; +#define OP_SOVER(mask) \ +if (another.syllable_ & mask) { \ +newSyllable = (newSyllable & ~mask) | (another.syllable_ & mask); \ +} + OP_SOVER(ConsonantMask); + OP_SOVER(MiddleVowelMask); + OP_SOVER(VowelMask); + OP_SOVER(ToneMarkerMask); +#undef OP_SOVER + return BopomofoSyllable(newSyllable); + } + + BopomofoSyllable& operator+=(const BopomofoSyllable& another) { +#define OPE_SOVER(mask) \ +if (another.syllable_ & mask) { \ +syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \ +} + OPE_SOVER(ConsonantMask); + OPE_SOVER(MiddleVowelMask); + OPE_SOVER(VowelMask); + OPE_SOVER(ToneMarkerMask); +#undef OPE_SOVER + return *this; + } + + uint16_t absoluteOrder() const { + // turn BPMF syllable into a 4*14*4*22 number + return (uint16_t)(syllable_ & ConsonantMask) + + (uint16_t)((syllable_ & MiddleVowelMask) >> 5) * 22 + + (uint16_t)((syllable_ & VowelMask) >> 7) * 22 * 4 + + (uint16_t)((syllable_ & ToneMarkerMask) >> 11) * 22 * 4 * 14; + } + + const std::string absoluteOrderString() const { + // 5*14*4*22 = 6160, we use a 79*79 encoding to represent that + uint16_t order = absoluteOrder(); + char low = 48 + static_cast(order % 79); + char high = 48 + static_cast(order / 79); + std::string result(2, ' '); + result[0] = low; + result[1] = high; + return result; + } + + static BopomofoSyllable FromAbsoluteOrder(uint16_t order) { + return BopomofoSyllable((order % 22) | ((order / 22) % 4) << 5 | + ((order / (22 * 4)) % 14) << 7 | + ((order / (22 * 4 * 14)) % 5) << 11); + } + + static BopomofoSyllable FromAbsoluteOrderString(const std::string& str) { + if (str.length() != 2) return BopomofoSyllable(); - class BopomofoSyllable { - public: - typedef unsigned int Component; - BopomofoSyllable(Component syllable = 0) - : m_syllable(syllable) - { - } - - BopomofoSyllable(const BopomofoSyllable& another) - : m_syllable(another.m_syllable) - { - } - - ~BopomofoSyllable() - { - } - - BopomofoSyllable& operator=(const BopomofoSyllable& another) - { - m_syllable = another.m_syllable; - return *this; - } - - // takes the ASCII-form, "v"-tolerant, TW-style Hanyu Pinyin (fong, pong, bong acceptable) - static const BopomofoSyllable FromHanyuPinyin(const string& str); - - // TO DO: Support accented vowels - const string HanyuPinyinString(bool includesTone, bool useVForUUmlaut) const; - // const string HanyuPinyinString(bool includesTone, bool useVForUUmlaut, bool composeAccentedVowel) const; - - // PHT = Pai-hua-tsi - static const BopomofoSyllable FromPHT(const string& str); - const string PHTString(bool includesTone) const; - - static const BopomofoSyllable FromComposedString(const string& str); - const string composedString() const; - - void clear() - { - m_syllable = 0; - } - - bool isEmpty() const - { - return !m_syllable; - } - - bool hasConsonant() const - { - return !!(m_syllable & ConsonantMask); - } - - bool hasMiddleVowel() const - { - return !!(m_syllable & MiddleVowelMask); - } - bool hasVowel() const - { - return !!(m_syllable & VowelMask); - } - - bool hasToneMarker() const - { - return !!(m_syllable & ToneMarkerMask); - } - - Component consonantComponent() const - { - return m_syllable & ConsonantMask; - } - - Component middleVowelComponent() const - { - return m_syllable & MiddleVowelMask; - } - - Component vowelComponent() const - { - return m_syllable & VowelMask; - } - - Component toneMarkerComponent() const - { - return m_syllable & ToneMarkerMask; - } - - bool operator==(const BopomofoSyllable& another) const - { - return m_syllable == another.m_syllable; - } + return FromAbsoluteOrder((uint16_t)(str[1] - 48) * 79 + + (uint16_t)(str[0] - 48)); + } + + friend std::ostream& operator<<(std::ostream& stream, + const BopomofoSyllable& syllable); + + static constexpr Component + ConsonantMask = 0x001f, // 0000 0000 0001 1111, 21 consonants + MiddleVowelMask = 0x0060, // 0000 0000 0110 0000, 3 middle vowels + VowelMask = 0x0780, // 0000 0111 1000 0000, 13 vowels + ToneMarkerMask = 0x3800, // 0011 1000 0000 0000, 5 tones (tone1 = 0x00) + B = 0x0001, P = 0x0002, M = 0x0003, F = 0x0004, D = 0x0005, T = 0x0006, + N = 0x0007, L = 0x0008, G = 0x0009, K = 0x000a, H = 0x000b, J = 0x000c, + Q = 0x000d, X = 0x000e, ZH = 0x000f, CH = 0x0010, SH = 0x0011, R = 0x0012, + Z = 0x0013, C = 0x0014, S = 0x0015, I = 0x0020, U = 0x0040, + UE = 0x0060, // ue = u umlaut (we use the German convention here as an + // ersatz to the /ju:/ sound) + A = 0x0080, O = 0x0100, ER = 0x0180, E = 0x0200, AI = 0x0280, EI = 0x0300, + AO = 0x0380, OU = 0x0400, AN = 0x0480, EN = 0x0500, ANG = 0x0580, + ENG = 0x0600, ERR = 0x0680, Tone1 = 0x0000, Tone2 = 0x0800, + Tone3 = 0x1000, Tone4 = 0x1800, Tone5 = 0x2000; + +protected: + Component syllable_; +}; - bool operator!=(const BopomofoSyllable& another) const - { - return m_syllable != another.m_syllable; - } - - bool isOverlappingWith(const BopomofoSyllable& another) const - { - #define IOW_SAND(mask) ((m_syllable & mask) && (another.m_syllable & mask)) - return IOW_SAND(ConsonantMask) || IOW_SAND(MiddleVowelMask) || IOW_SAND(VowelMask) || IOW_SAND(ToneMarkerMask); - #undef IOW_SAND - } - - // consonants J, Q, X all require the existence of vowel I or UE - bool belongsToJQXClass() const - { - Component consonant = m_syllable & ConsonantMask; - return (consonant == J || consonant == Q || consonant == X); - } - - // zi, ci, si, chi, chi, shi, ri - bool belongsToZCSRClass() const - { - Component consonant = m_syllable & ConsonantMask; - return (consonant >= ZH && consonant <= S); - } - - Component maskType() const - { - Component mask = 0; - mask |= (m_syllable & ConsonantMask) ? ConsonantMask : 0; - mask |= (m_syllable & MiddleVowelMask) ? MiddleVowelMask : 0; - mask |= (m_syllable & VowelMask) ? VowelMask : 0; - mask |= (m_syllable & ToneMarkerMask) ? ToneMarkerMask : 0; - return mask; - } - - const BopomofoSyllable operator+(const BopomofoSyllable& another) const - { - Component newSyllable = m_syllable; - #define OP_SOVER(mask) if (another.m_syllable & mask) newSyllable = (newSyllable & ~mask) | (another.m_syllable & mask) - OP_SOVER(ConsonantMask); - OP_SOVER(MiddleVowelMask); - OP_SOVER(VowelMask); - OP_SOVER(ToneMarkerMask); - #undef OP_SOVER - return BopomofoSyllable(newSyllable); - } - - BopomofoSyllable& operator+=(const BopomofoSyllable& another) - { - #define OPE_SOVER(mask) if (another.m_syllable & mask) m_syllable = (m_syllable & ~mask) | (another.m_syllable & mask) - OPE_SOVER(ConsonantMask); - OPE_SOVER(MiddleVowelMask); - OPE_SOVER(VowelMask); - OPE_SOVER(ToneMarkerMask); - #undef OPE_SOVER - return *this; - } +inline std::ostream& operator<<(std::ostream& stream, + const BopomofoSyllable& syllable) { + stream << syllable.composedString(); + return stream; +} - short absoluteOrder() const - { - // turn BPMF syllable into a 4*14*4*22 number - return (short)(m_syllable & ConsonantMask) + - (short)((m_syllable & MiddleVowelMask) >> 5) * 22 + - (short)((m_syllable & VowelMask) >> 7) * 22 * 4 + - (short)((m_syllable & ToneMarkerMask) >> 11) * 22 * 4 * 14; - } - - const string absoluteOrderString() const - { - // 5*14*4*22 = 6160, we use a 79*79 encoding to represent that - short order = absoluteOrder(); - char low = 48 + (char)(order % 79); - char high = 48 + (char)(order / 79); - string result(2, ' '); - result[0] = low; - result[1] = high; - return result; - } - - static BopomofoSyllable FromAbsoluteOrder(short order) - { - return BopomofoSyllable( - (order % 22) | - ((order / 22) % 4) << 5 | - ((order / (22 * 4)) % 14) << 7 | - ((order / (22 * 4 * 14)) % 5) << 11 - ); - } - - static BopomofoSyllable FromAbsoluteOrderString(const string& str) - { - if (str.length() != 2) - return BopomofoSyllable(); - - return FromAbsoluteOrder((short)(str[1] - 48) * 79 + (short)(str[0] - 48)); - } +typedef BopomofoSyllable BPMF; - friend ostream& operator<<(ostream& stream, const BopomofoSyllable& syllable); +typedef std::map > BopomofoKeyToComponentMap; +typedef std::map BopomofoComponentToKeyMap; - static const Component - ConsonantMask = 0x001f, // 0000 0000 0001 1111, 21 consonants - MiddleVowelMask = 0x0060, // 0000 0000 0110 0000, 3 middle vowels - VowelMask = 0x0780, // 0000 0111 1000 0000, 13 vowels - ToneMarkerMask = 0x3800, // 0011 1000 0000 0000, 5 tones (tone1 = 0x00) - B = 0x0001, P = 0x0002, M = 0x0003, F = 0x0004, - D = 0x0005, T = 0x0006, N = 0x0007, L = 0x0008, - G = 0x0009, K = 0x000a, H = 0x000b, - J = 0x000c, Q = 0x000d, X = 0x000e, - ZH = 0x000f, CH = 0x0010, SH = 0x0011, R = 0x0012, - Z = 0x0013, C = 0x0014, S = 0x0015, - I = 0x0020, U = 0x0040, UE = 0x0060, // ue = u umlaut (we use the German convention here as an ersatz to the /ju:/ sound) - A = 0x0080, O = 0x0100, ER = 0x0180, E = 0x0200, - AI = 0x0280, EI = 0x0300, AO = 0x0380, OU = 0x0400, - AN = 0x0480, EN = 0x0500, ANG = 0x0580, ENG = 0x0600, - ERR = 0x0680, - Tone1 = 0x0000, Tone2 = 0x0800, Tone3 = 0x1000, Tone4 = 0x1800, Tone5 = 0x2000; - - protected: - Component m_syllable; - }; - - inline ostream& operator<<(ostream& stream, const BopomofoSyllable& syllable) - { - stream << syllable.composedString(); - return stream; - } - - typedef BopomofoSyllable BPMF; +class BopomofoKeyboardLayout { +public: + static const BopomofoKeyboardLayout* StandardLayout(); + static const BopomofoKeyboardLayout* ETenLayout(); + static const BopomofoKeyboardLayout* HsuLayout(); + static const BopomofoKeyboardLayout* ETen26Layout(); + static const BopomofoKeyboardLayout* IBMLayout(); + static const BopomofoKeyboardLayout* HanyuPinyinLayout(); + + BopomofoKeyboardLayout(const BopomofoKeyToComponentMap& ktcm, + const std::string& name) + : m_keyToComponent(ktcm), m_name(name) { + for (BopomofoKeyToComponentMap::const_iterator miter = + m_keyToComponent.begin(); + miter != m_keyToComponent.end(); ++miter) + for (std::vector::const_iterator viter = + (*miter).second.begin(); + viter != (*miter).second.end(); ++viter) + m_componentToKey[*viter] = (*miter).first; + } + + const std::string name() const { return m_name; } + + char componentToKey(BPMF::Component component) const { + BopomofoComponentToKeyMap::const_iterator iter = + m_componentToKey.find(component); + return (iter == m_componentToKey.end()) ? 0 : (*iter).second; + } + + const std::vector keyToComponents(char key) const { + BopomofoKeyToComponentMap::const_iterator iter = m_keyToComponent.find(key); + return (iter == m_keyToComponent.end()) ? std::vector() + : (*iter).second; + } + + const std::string keySequenceFromSyllable(BPMF syllable) const { + std::string sequence; - typedef map > BopomofoKeyToComponentMap; - typedef map BopomofoComponentToKeyMap; + BPMF::Component c; + char k; +#define STKS_COMBINE(component) \ +if ((c = component)) { \ +if ((k = componentToKey(c))) sequence += std::string(1, k); \ +} + STKS_COMBINE(syllable.consonantComponent()); + STKS_COMBINE(syllable.middleVowelComponent()); + STKS_COMBINE(syllable.vowelComponent()); + STKS_COMBINE(syllable.toneMarkerComponent()); +#undef STKS_COMBINE + return sequence; + } + + const BPMF syllableFromKeySequence(const std::string& sequence) const { + BPMF syllable; - class BopomofoKeyboardLayout { - public: - static void FinalizeLayouts(); - static const BopomofoKeyboardLayout* StandardLayout(); - static const BopomofoKeyboardLayout* ETenLayout(); - static const BopomofoKeyboardLayout* HsuLayout(); - static const BopomofoKeyboardLayout* ETen26Layout(); - static const BopomofoKeyboardLayout* IBMLayout(); - static const BopomofoKeyboardLayout* HanyuPinyinLayout(); + for (std::string::const_iterator iter = sequence.begin(); + iter != sequence.end(); ++iter) { + bool beforeSeqHasIorUE = sequenceContainsIorUE(sequence.begin(), iter); + bool aheadSeqHasIorUE = sequenceContainsIorUE(iter + 1, sequence.end()); - // recognizes (case-insensitive): standard, eten, hsu, eten26, ibm - static const BopomofoKeyboardLayout* LayoutForName(const string& name); + std::vector components = keyToComponents(*iter); - BopomofoKeyboardLayout(const BopomofoKeyToComponentMap& ktcm, const string& name) - : m_keyToComponent(ktcm) - , m_name(name) - { - for (BopomofoKeyToComponentMap::const_iterator miter = m_keyToComponent.begin() ; miter != m_keyToComponent.end() ; ++miter) - for (vector::const_iterator viter = (*miter).second.begin() ; viter != (*miter).second.end() ; ++viter) - m_componentToKey[*viter] = (*miter).first; - } + if (!components.size()) continue; - const string name() const - { - return m_name; + if (components.size() == 1) { + syllable += BPMF(components[0]); + continue; } - char componentToKey(BPMF::Component component) const - { - BopomofoComponentToKeyMap::const_iterator iter = m_componentToKey.find(component); - return (iter == m_componentToKey.end()) ? 0 : (*iter).second; - } + BPMF head = BPMF(components[0]); + BPMF follow = BPMF(components[1]); + BPMF ending = components.size() > 2 ? BPMF(components[2]) : follow; - const vector keyToComponents(char key) const - { - BopomofoKeyToComponentMap::const_iterator iter = m_keyToComponent.find(key); - return (iter == m_keyToComponent.end()) ? vector() : (*iter).second; + // apply the I/UE + E rule + if (head.vowelComponent() == BPMF::E && + follow.vowelComponent() != BPMF::E) { + syllable += beforeSeqHasIorUE ? head : follow; + continue; } - const string keySequenceFromSyllable(BPMF syllable) const - { - string sequence; - - BPMF::Component c; - char k; - #define STKS_COMBINE(component) if ((c = component)) { if ((k = componentToKey(c))) sequence += string(1, k); } - STKS_COMBINE(syllable.consonantComponent()); - STKS_COMBINE(syllable.middleVowelComponent()); - STKS_COMBINE(syllable.vowelComponent()); - STKS_COMBINE(syllable.toneMarkerComponent()); - #undef STKS_COMBINE - return sequence; + if (head.vowelComponent() != BPMF::E && + follow.vowelComponent() == BPMF::E) { + syllable += beforeSeqHasIorUE ? follow : head; + continue; } - const BPMF syllableFromKeySequence(const string& sequence) const - { - BPMF syllable; - - for (string::const_iterator iter = sequence.begin() ; iter != sequence.end() ; ++iter) - { - bool beforeSeqHasIorUE = sequenceContainsIorUE(sequence.begin(), iter); - bool aheadSeqHasIorUE = sequenceContainsIorUE(iter + 1, sequence.end()); - - vector components = keyToComponents(*iter); - - if (!components.size()) - continue; - - if (components.size() == 1) { - syllable += BPMF(components[0]); - continue; - } - - BPMF head = BPMF(components[0]); - BPMF follow = BPMF(components[1]); - BPMF ending = components.size() > 2 ? BPMF(components[2]) : follow; - - // apply the I/UE + E rule - if (head.vowelComponent() == BPMF::E && follow.vowelComponent() != BPMF::E) - { - syllable += beforeSeqHasIorUE ? head : follow; - continue; - } - - if (head.vowelComponent() != BPMF::E && follow.vowelComponent() == BPMF::E) - { - syllable += beforeSeqHasIorUE ? follow : head; - continue; - } - - // apply the J/Q/X + I/UE rule, only two components are allowed in the components vector here - if (head.belongsToJQXClass() && !follow.belongsToJQXClass()) { - if (!syllable.isEmpty()) { - if (ending != follow) - syllable += ending; - } - else { - syllable += aheadSeqHasIorUE ? head : follow; - } - - continue; - } - - if (!head.belongsToJQXClass() && follow.belongsToJQXClass()) { - if (!syllable.isEmpty()) { - if (ending != follow) - syllable += ending; - } - else { - syllable += aheadSeqHasIorUE ? follow : head; - } - - continue; - } - - // the nasty issue of only one char in the buffer - if (iter == sequence.begin() && iter + 1 == sequence.end()) { - if (head.hasVowel() || follow.hasToneMarker() || head.belongsToZCSRClass()) - syllable += head; - else { - if (follow.hasVowel() || ending.hasToneMarker()) - syllable += follow; - else - syllable += ending; - } - - - continue; - } - - if (!(syllable.maskType() & head.maskType()) && !endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end())) { - syllable += head; - } - else { - if (endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end()) && head.belongsToZCSRClass() && syllable.isEmpty()) { - syllable += head; - } - else if (syllable.maskType() < follow.maskType()) { - syllable += follow; - } - else { - syllable += ending; - } - } + // apply the J/Q/X + I/UE rule, only two components are allowed in the + // components vector here + if (head.belongsToJQXClass() && !follow.belongsToJQXClass()) { + if (!syllable.isEmpty()) { + if (ending != follow) syllable += ending; + } else { + syllable += aheadSeqHasIorUE ? head : follow; } - // heuristics for Hsu keyboard layout - if (this == HsuLayout()) { - // fix the left out L to ERR when it has sound, and GI, GUE -> JI, JUE - if (syllable.vowelComponent() == BPMF::ENG && !syllable.hasConsonant() && !syllable.hasMiddleVowel()) { - syllable += BPMF(BPMF::ERR); - } - else if (syllable.consonantComponent() == BPMF::G && (syllable.middleVowelComponent() == BPMF::I || syllable.middleVowelComponent() == BPMF::UE)) { - syllable += BPMF(BPMF::J); - } - } - - - return syllable; + continue; } - - protected: - bool endAheadOrAheadHasToneMarkKey(string::const_iterator ahead, string::const_iterator end) const - { - if (ahead == end) - return true; - - char tone1 = componentToKey(BPMF::Tone1); - char tone2 = componentToKey(BPMF::Tone2); - char tone3 = componentToKey(BPMF::Tone3); - char tone4 = componentToKey(BPMF::Tone4); - char tone5 = componentToKey(BPMF::Tone5); + if (!head.belongsToJQXClass() && follow.belongsToJQXClass()) { + if (!syllable.isEmpty()) { + if (ending != follow) syllable += ending; + } else { + syllable += aheadSeqHasIorUE ? follow : head; + } - if (tone1) - if (*ahead == tone1) return true; - - if (*ahead == tone2 || *ahead == tone3 || *ahead == tone4 || *ahead == tone5) - return true; - - return false; - } - - bool sequenceContainsIorUE(string::const_iterator start, string::const_iterator end) const - { - char iChar = componentToKey(BPMF::I); - char ueChar = componentToKey(BPMF::UE); + continue; + } + + // the nasty issue of only one char in the buffer + if (iter == sequence.begin() && iter + 1 == sequence.end()) { + if (head.hasVowel() || follow.hasToneMarker() || + head.belongsToZCSRClass()) { + syllable += head; + } else { + if (follow.hasVowel() || ending.hasToneMarker()) { + syllable += follow; + } else { + syllable += ending; + } + } - for (; start != end; ++start) - if (*start == iChar || *start == ueChar) - return true; - return false; + continue; + } + + if (!(syllable.maskType() & head.maskType()) && + !endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end())) { + syllable += head; + } else { + if (endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end()) && + head.belongsToZCSRClass() && syllable.isEmpty()) { + syllable += head; + } else if (syllable.maskType() < follow.maskType()) { + syllable += follow; + } else { + syllable += ending; + } } + } + + // heuristics for Hsu keyboard layout + if (this == HsuLayout()) { + // fix the left out L to ERR when it has sound, and GI, GUE -> JI, JUE + if (syllable.vowelComponent() == BPMF::ENG && !syllable.hasConsonant() && + !syllable.hasMiddleVowel()) { + syllable += BPMF(BPMF::ERR); + } else if (syllable.consonantComponent() == BPMF::G && + (syllable.middleVowelComponent() == BPMF::I || + syllable.middleVowelComponent() == BPMF::UE)) { + syllable += BPMF(BPMF::J); + } + } + + return syllable; + } + +protected: + bool endAheadOrAheadHasToneMarkKey(std::string::const_iterator ahead, + std::string::const_iterator end) const { + if (ahead == end) return true; + + char tone1 = componentToKey(BPMF::Tone1); + char tone2 = componentToKey(BPMF::Tone2); + char tone3 = componentToKey(BPMF::Tone3); + char tone4 = componentToKey(BPMF::Tone4); + char tone5 = componentToKey(BPMF::Tone5); - string m_name; - BopomofoKeyToComponentMap m_keyToComponent; - BopomofoComponentToKeyMap m_componentToKey; + if (tone1) + if (*ahead == tone1) return true; - static const BopomofoKeyboardLayout* c_StandardLayout; - static const BopomofoKeyboardLayout* c_ETenLayout; - static const BopomofoKeyboardLayout* c_HsuLayout; - static const BopomofoKeyboardLayout* c_ETen26Layout; - static const BopomofoKeyboardLayout* c_IBMLayout; + if (*ahead == tone2 || *ahead == tone3 || *ahead == tone4 || + *ahead == tone5) + return true; - // this is essentially an empty layout, but we use pointer semantic to tell the differences--and pass on the responsibility to BopomofoReadingBuffer - static const BopomofoKeyboardLayout* c_HanyuPinyinLayout; - }; + return false; + } + + bool sequenceContainsIorUE(std::string::const_iterator start, + std::string::const_iterator end) const { + char iChar = componentToKey(BPMF::I); + char ueChar = componentToKey(BPMF::UE); - class BopomofoReadingBuffer { - public: - BopomofoReadingBuffer(const BopomofoKeyboardLayout* layout) - : m_layout(layout) - , m_pinyinMode(false) - { - if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) { - m_pinyinMode = true; - m_pinyinSequence = ""; - } - } - - void setKeyboardLayout(const BopomofoKeyboardLayout* layout) - { - m_layout = layout; + for (; start != end; ++start) + if (*start == iChar || *start == ueChar) return true; + return false; + } + + std::string m_name; + BopomofoKeyToComponentMap m_keyToComponent; + BopomofoComponentToKeyMap m_componentToKey; +}; - if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) { - m_pinyinMode = true; - m_pinyinSequence = ""; - } - } - - bool isValidKey(char k) const - { - if (!m_pinyinMode) { - return m_layout ? (m_layout->keyToComponents(k)).size() > 0 : false; - } - - char lk = tolower(k); - if (lk >= 'a' && lk <= 'z') { - // if a tone marker is already in place - if (m_pinyinSequence.length()) { - char lastc = m_pinyinSequence[m_pinyinSequence.length() - 1]; - if (lastc >= '2' && lastc <= '5') { - return false; - } - return true; - } - return true; - } - - if (m_pinyinSequence.length() && (lk >= '2' && lk <= '5')) { - return true; - } - - return false; - } - - bool combineKey(char k) - { - if (!isValidKey(k)) +class BopomofoReadingBuffer { +public: + explicit BopomofoReadingBuffer(const BopomofoKeyboardLayout* layout) + : layout_(layout), pinyin_mode_(false) { + if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) { + pinyin_mode_ = true; + pinyin_sequence_ = ""; + } + } + + void setKeyboardLayout(const BopomofoKeyboardLayout* layout) { + layout_ = layout; + + if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout()) { + pinyin_mode_ = true; + pinyin_sequence_ = ""; + } + } + + bool isValidKey(char k) const { + if (!pinyin_mode_) { + return layout_ ? (layout_->keyToComponents(k)).size() > 0 : false; + } + + char lk = tolower(k); + if (lk >= 'a' && lk <= 'z') { + // if a tone marker is already in place + if (pinyin_sequence_.length()) { + char lastc = pinyin_sequence_[pinyin_sequence_.length() - 1]; + if (lastc >= '2' && lastc <= '5') { return false; - - if (m_pinyinMode) { - m_pinyinSequence += string(1, tolower(k)); - m_syllable = BPMF::FromHanyuPinyin(m_pinyinSequence); - return true; } - - string sequence = m_layout->keySequenceFromSyllable(m_syllable) + string(1, k); - m_syllable = m_layout->syllableFromKeySequence(sequence); return true; } - - void clear() - { - m_pinyinSequence.clear(); - m_syllable.clear(); - } - - void backspace() - { - if (!m_layout) - return; - - if (m_pinyinMode) { - if (m_pinyinSequence.length()) { - m_pinyinSequence = m_pinyinSequence.substr(0, m_pinyinSequence.length() - 1); - } - - m_syllable = BPMF::FromHanyuPinyin(m_pinyinSequence); - return; - } - - string sequence = m_layout->keySequenceFromSyllable(m_syllable); - if (sequence.length()) { - sequence = sequence.substr(0, sequence.length() - 1); - m_syllable = m_layout->syllableFromKeySequence(sequence); - } - } - - bool isEmpty() const - { - return m_syllable.isEmpty(); - } - - const string composedString() const - { - if (m_pinyinMode) { - return m_pinyinSequence; - } - - return m_syllable.composedString(); - } - - const BPMF syllable() const - { - return m_syllable; - } - - const string standardLayoutQueryString() const - { - return BopomofoKeyboardLayout::StandardLayout()->keySequenceFromSyllable(m_syllable); - } - - const string absoluteOrderQueryString() const - { - return m_syllable.absoluteOrderString(); - } - - bool hasToneMarker() const - { - return m_syllable.hasToneMarker(); + return true; + } + + if (pinyin_sequence_.length() && (lk >= '2' && lk <= '5')) { + return true; + } + + return false; + } + + bool combineKey(char k) { + if (!isValidKey(k)) return false; + + if (pinyin_mode_) { + pinyin_sequence_ += std::string(1, tolower(k)); + syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_); + return true; + } + + std::string sequence = + layout_->keySequenceFromSyllable(syllable_) + std::string(1, k); + syllable_ = layout_->syllableFromKeySequence(sequence); + return true; + } + + void clear() { + pinyin_sequence_.clear(); + syllable_.clear(); + } + + void backspace() { + if (!layout_) return; + + if (pinyin_mode_) { + if (pinyin_sequence_.length()) { + pinyin_sequence_ = + pinyin_sequence_.substr(0, pinyin_sequence_.length() - 1); } - protected: - const BopomofoKeyboardLayout* m_layout; - BPMF m_syllable; - - bool m_pinyinMode; - string m_pinyinSequence; - }; + syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_); + return; + } + + std::string sequence = layout_->keySequenceFromSyllable(syllable_); + if (sequence.length()) { + sequence = sequence.substr(0, sequence.length() - 1); + syllable_ = layout_->syllableFromKeySequence(sequence); + } } -} + + bool isEmpty() const { return syllable_.isEmpty(); } + + const std::string composedString() const { + if (pinyin_mode_) { + return pinyin_sequence_; + } + + return syllable_.composedString(); + } + + const BPMF syllable() const { return syllable_; } + + const std::string standardLayoutQueryString() const { + return BopomofoKeyboardLayout::StandardLayout()->keySequenceFromSyllable(syllable_); + } + + const std::string absoluteOrderQueryString() const { + return syllable_.absoluteOrderString(); + } + + bool hasToneMarker() const { return syllable_.hasToneMarker(); } + +protected: + const BopomofoKeyboardLayout* layout_; + BPMF syllable_; + + bool pinyin_mode_; + std::string pinyin_sequence_; +}; +} // namespace Mandarin +} // namespace Taiyan -#endif +#endif // MANDARIN_H_ diff --git a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.mm b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.mm index 8e7494f96..3d86d92ef 100644 --- a/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.mm +++ b/Source/3rdParty/OpenVanilla/Mandarin/Mandarin.mm @@ -5,28 +5,24 @@ * All rights reserved. See "LICENSE.TXT" for details. */ -#include -#include #include "Mandarin.hh" -#include "OVUTF8Helper.h" -#include "OVWildcard.h" +#include +#include namespace Taiyan { namespace Mandarin { - -using namespace OpenVanilla; class PinyinParseHelper { public: - static const bool ConsumePrefix(string &target, const string &prefix) - { + static const bool ConsumePrefix(std::string& target, const std::string& prefix) { if (target.length() < prefix.length()) { return false; } if (target.substr(0, prefix.length()) == prefix) { - target = target.substr(prefix.length(), target.length() - prefix.length()); + target = + target.substr(prefix.length(), target.length() - prefix.length()); return true; } @@ -37,24 +33,22 @@ public: class BopomofoCharacterMap { public: static const BopomofoCharacterMap& SharedInstance(); - - map componentToCharacter; - map characterToComponent; - + + std::map componentToCharacter; + std::map characterToComponent; + protected: BopomofoCharacterMap(); - static BopomofoCharacterMap* c_map; }; -const BPMF BPMF::FromHanyuPinyin(const string& str) -{ +const BPMF BPMF::FromHanyuPinyin(const std::string& str) { if (!str.length()) { return BPMF(); } - string pinyin = str; + std::string pinyin = str; transform(pinyin.begin(), pinyin.end(), pinyin.begin(), ::tolower); - + BPMF::Component firstComponent = 0; BPMF::Component secondComponent = 0; BPMF::Component thirdComponent = 0; @@ -62,39 +56,99 @@ const BPMF BPMF::FromHanyuPinyin(const string& str) // lookup consonants and consume them bool independentConsonant = false; - - // the y exceptions fist - if (0) {} - else if (PinyinParseHelper::ConsumePrefix(pinyin, "yuan")) { secondComponent = BPMF::UE; thirdComponent = BPMF::AN; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ying")) { secondComponent = BPMF::I; thirdComponent = BPMF::ENG; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "yung")) { secondComponent = BPMF::UE; thirdComponent = BPMF::ENG; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "yong")) { secondComponent = BPMF::UE; thirdComponent = BPMF::ENG; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "yue")) { secondComponent = BPMF::UE; thirdComponent = BPMF::E; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "yun")) { secondComponent = BPMF::UE; thirdComponent = BPMF::EN; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "you")) { secondComponent = BPMF::I; thirdComponent = BPMF::OU; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "yu")) { secondComponent = BPMF::UE; } + // the y exceptions fist + if (0) { + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yuan")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::AN; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ying")) { + secondComponent = BPMF::I; + thirdComponent = BPMF::ENG; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yung")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::ENG; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yong")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::ENG; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yue")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::E; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yun")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::EN; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "you")) { + secondComponent = BPMF::I; + thirdComponent = BPMF::OU; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "yu")) { + secondComponent = BPMF::UE; + } // try the first character char c = pinyin.length() ? pinyin[0] : 0; switch (c) { - case 'b': firstComponent = BPMF::B; pinyin = pinyin.substr(1); break; - case 'p': firstComponent = BPMF::P; pinyin = pinyin.substr(1); break; - case 'm': firstComponent = BPMF::M; pinyin = pinyin.substr(1); break; - case 'f': firstComponent = BPMF::F; pinyin = pinyin.substr(1); break; - case 'd': firstComponent = BPMF::D; pinyin = pinyin.substr(1); break; - case 't': firstComponent = BPMF::T; pinyin = pinyin.substr(1); break; - case 'n': firstComponent = BPMF::N; pinyin = pinyin.substr(1); break; - case 'l': firstComponent = BPMF::L; pinyin = pinyin.substr(1); break; - case 'g': firstComponent = BPMF::G; pinyin = pinyin.substr(1); break; - case 'k': firstComponent = BPMF::K; pinyin = pinyin.substr(1); break; - case 'h': firstComponent = BPMF::H; pinyin = pinyin.substr(1); break; - case 'j': firstComponent = BPMF::J; pinyin = pinyin.substr(1); break; - case 'q': firstComponent = BPMF::Q; pinyin = pinyin.substr(1); break; - case 'x': firstComponent = BPMF::X; pinyin = pinyin.substr(1); break; - - // special hanlding for w and y - case 'w': secondComponent = BPMF::U; pinyin = pinyin.substr(1); break; + case 'b': + firstComponent = BPMF::B; + pinyin = pinyin.substr(1); + break; + case 'p': + firstComponent = BPMF::P; + pinyin = pinyin.substr(1); + break; + case 'm': + firstComponent = BPMF::M; + pinyin = pinyin.substr(1); + break; + case 'f': + firstComponent = BPMF::F; + pinyin = pinyin.substr(1); + break; + case 'd': + firstComponent = BPMF::D; + pinyin = pinyin.substr(1); + break; + case 't': + firstComponent = BPMF::T; + pinyin = pinyin.substr(1); + break; + case 'n': + firstComponent = BPMF::N; + pinyin = pinyin.substr(1); + break; + case 'l': + firstComponent = BPMF::L; + pinyin = pinyin.substr(1); + break; + case 'g': + firstComponent = BPMF::G; + pinyin = pinyin.substr(1); + break; + case 'k': + firstComponent = BPMF::K; + pinyin = pinyin.substr(1); + break; + case 'h': + firstComponent = BPMF::H; + pinyin = pinyin.substr(1); + break; + case 'j': + firstComponent = BPMF::J; + pinyin = pinyin.substr(1); + break; + case 'q': + firstComponent = BPMF::Q; + pinyin = pinyin.substr(1); + break; + case 'x': + firstComponent = BPMF::X; + pinyin = pinyin.substr(1); + break; + + // special hanlding for w and y + case 'w': + secondComponent = BPMF::U; + pinyin = pinyin.substr(1); + break; case 'y': if (!secondComponent && !thirdComponent) { secondComponent = BPMF::I; @@ -104,153 +158,255 @@ const BPMF BPMF::FromHanyuPinyin(const string& str) } // then we try ZH, CH, SH, R, Z, C, S (in that order) - if (0) {} - else if (PinyinParseHelper::ConsumePrefix(pinyin, "zh")) { firstComponent = BPMF::ZH; independentConsonant = true; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ch")) { firstComponent = BPMF::CH; independentConsonant = true; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "sh")) { firstComponent = BPMF::SH; independentConsonant = true; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "r")) { firstComponent = BPMF::R; independentConsonant = true; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "z")) { firstComponent = BPMF::Z; independentConsonant = true; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "c")) { firstComponent = BPMF::C; independentConsonant = true; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "s")) { firstComponent = BPMF::S; independentConsonant = true; } - - // consume exceptions first: (ien, in), (iou, iu), (uen, un), (veng, iong), (ven, vn), (uei, ui), ung - // but longer sequence takes precedence - if (0) {} - else if (PinyinParseHelper::ConsumePrefix(pinyin, "veng")) { secondComponent = BPMF::UE; thirdComponent = BPMF::ENG; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "iong")) { secondComponent = BPMF::UE; thirdComponent = BPMF::ENG; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ing")) { secondComponent = BPMF::I; thirdComponent = BPMF::ENG; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ien")) { secondComponent = BPMF::I; thirdComponent = BPMF::EN; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "iou")) { secondComponent = BPMF::I; thirdComponent = BPMF::OU; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "uen")) { secondComponent = BPMF::U; thirdComponent = BPMF::EN; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ven")) { secondComponent = BPMF::UE; thirdComponent = BPMF::EN; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "uei")) { secondComponent = BPMF::U; thirdComponent = BPMF::EI; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ung")) { + if (0) { + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "zh")) { + firstComponent = BPMF::ZH; + independentConsonant = true; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ch")) { + firstComponent = BPMF::CH; + independentConsonant = true; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "sh")) { + firstComponent = BPMF::SH; + independentConsonant = true; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "r")) { + firstComponent = BPMF::R; + independentConsonant = true; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "z")) { + firstComponent = BPMF::Z; + independentConsonant = true; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "c")) { + firstComponent = BPMF::C; + independentConsonant = true; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "s")) { + firstComponent = BPMF::S; + independentConsonant = true; + } + + // consume exceptions first: (ien, in), (iou, iu), (uen, un), (veng, iong), + // (ven, vn), (uei, ui), ung but longer sequence takes precedence + if (0) { + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "veng")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::ENG; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "iong")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::ENG; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ing")) { + secondComponent = BPMF::I; + thirdComponent = BPMF::ENG; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ien")) { + secondComponent = BPMF::I; + thirdComponent = BPMF::EN; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "iou")) { + secondComponent = BPMF::I; + thirdComponent = BPMF::OU; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "uen")) { + secondComponent = BPMF::U; + thirdComponent = BPMF::EN; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ven")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::EN; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "uei")) { + secondComponent = BPMF::U; + thirdComponent = BPMF::EI; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ung")) { // f exception if (firstComponent == BPMF::F) { - thirdComponent = BPMF::ENG; - } - else { - secondComponent = BPMF::U; thirdComponent = BPMF::ENG; + thirdComponent = BPMF::ENG; + } else { + secondComponent = BPMF::U; + thirdComponent = BPMF::ENG; } - } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ong")) { + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ong")) { // f exception if (firstComponent == BPMF::F) { - thirdComponent = BPMF::ENG; - } - else { + thirdComponent = BPMF::ENG; + } else { secondComponent = BPMF::U; thirdComponent = BPMF::ENG; } - } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "un")) { - if (firstComponent == BPMF::J || firstComponent == BPMF::Q || firstComponent == BPMF::X) { + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "un")) { + if (firstComponent == BPMF::J || firstComponent == BPMF::Q || + firstComponent == BPMF::X) { secondComponent = BPMF::UE; - } - else { + } else { secondComponent = BPMF::U; } thirdComponent = BPMF::EN; - } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "iu")) { secondComponent = BPMF::I; thirdComponent = BPMF::OU; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "in")) { secondComponent = BPMF::I; thirdComponent = BPMF::EN; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "vn")) { secondComponent = BPMF::UE; thirdComponent = BPMF::EN; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ui")) { secondComponent = BPMF::U; thirdComponent = BPMF::EI; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ue")) - { - secondComponent = BPMF::UE; thirdComponent = BPMF::E; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "iu")) { + secondComponent = BPMF::I; + thirdComponent = BPMF::OU; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "in")) { + secondComponent = BPMF::I; + thirdComponent = BPMF::EN; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "vn")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::EN; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ui")) { + secondComponent = BPMF::U; + thirdComponent = BPMF::EI; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ue")) { + secondComponent = BPMF::UE; + thirdComponent = BPMF::E; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, u8"ü")) { + secondComponent = BPMF::UE; } - #ifndef _MSC_VER - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ü")) { secondComponent = BPMF::UE; } - #else - else if (PinyinParseHelper::ConsumePrefix(pinyin, "\xc3\xbc")) { secondComponent = BPMF::UE; } - #endif - // then consume the middle component... - if (0) {} - else if (PinyinParseHelper::ConsumePrefix(pinyin, "i")) { secondComponent = independentConsonant ? 0 : BPMF::I; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "u")) { - if (firstComponent == BPMF::J || firstComponent == BPMF::Q || firstComponent == BPMF::X) { + if (0) { + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "i")) { + secondComponent = independentConsonant ? 0 : BPMF::I; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "u")) { + if (firstComponent == BPMF::J || firstComponent == BPMF::Q || + firstComponent == BPMF::X) { secondComponent = BPMF::UE; - } - else { + } else { secondComponent = BPMF::U; } + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "v")) { + secondComponent = BPMF::UE; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "v")) { secondComponent = BPMF::UE; } - + // the vowels, longer sequence takes precedence - if (0) {} - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ang")) { thirdComponent = BPMF::ANG; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "eng")) { thirdComponent = BPMF::ENG; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "err")) { thirdComponent = BPMF::ERR; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ai")) { thirdComponent = BPMF::AI; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ei")) { thirdComponent = BPMF::EI; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ao")) { thirdComponent = BPMF::AO; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "ou")) { thirdComponent = BPMF::OU; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "an")) { thirdComponent = BPMF::AN; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "en")) { thirdComponent = BPMF::EN; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "er")) { thirdComponent = BPMF::ERR; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "a")) { thirdComponent = BPMF::A; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "o")) { thirdComponent = BPMF::O; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "e")) { + if (0) { + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ang")) { + thirdComponent = BPMF::ANG; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "eng")) { + thirdComponent = BPMF::ENG; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "err")) { + thirdComponent = BPMF::ERR; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ai")) { + thirdComponent = BPMF::AI; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ei")) { + thirdComponent = BPMF::EI; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ao")) { + thirdComponent = BPMF::AO; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "ou")) { + thirdComponent = BPMF::OU; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "an")) { + thirdComponent = BPMF::AN; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "en")) { + thirdComponent = BPMF::EN; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "er")) { + thirdComponent = BPMF::ERR; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "a")) { + thirdComponent = BPMF::A; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "o")) { + thirdComponent = BPMF::O; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "e")) { if (secondComponent) { thirdComponent = BPMF::E; - } - else { + } else { thirdComponent = BPMF::ER; } } - + // at last! - if (0) {} - else if (PinyinParseHelper::ConsumePrefix(pinyin, "1")) { toneComponent = BPMF::Tone1; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "2")) { toneComponent = BPMF::Tone2; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "3")) { toneComponent = BPMF::Tone3; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "4")) { toneComponent = BPMF::Tone4; } - else if (PinyinParseHelper::ConsumePrefix(pinyin, "5")) { toneComponent = BPMF::Tone5; } - - return BPMF(firstComponent | secondComponent | thirdComponent | toneComponent); + if (0) { + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "1")) { + toneComponent = BPMF::Tone1; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "2")) { + toneComponent = BPMF::Tone2; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "3")) { + toneComponent = BPMF::Tone3; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "4")) { + toneComponent = BPMF::Tone4; + } else if (PinyinParseHelper::ConsumePrefix(pinyin, "5")) { + toneComponent = BPMF::Tone5; + } + + return BPMF(firstComponent | secondComponent | thirdComponent | + toneComponent); } -const string BPMF::HanyuPinyinString(bool includesTone, bool useVForUUmlaut) const -{ - string consonant, middle, vowel, tone; +const std::string BPMF::HanyuPinyinString(bool includesTone, + bool useVForUUmlaut) const { + std::string consonant, middle, vowel, tone; - Component cc = consonantComponent(), mvc = middleVowelComponent(), vc = vowelComponent(); + Component cc = consonantComponent(), mvc = middleVowelComponent(), + vc = vowelComponent(); bool hasNoMVCOrVC = !(mvc || vc); - switch (cc) { - case B: consonant = "b"; break; - case P: consonant = "p"; break; - case M: consonant = "m"; break; - case F: consonant = "f"; break; - case D: consonant = "d"; break; - case T: consonant = "t"; break; - case N: consonant = "n"; break; - case L: consonant = "l"; break; - case G: consonant = "g"; break; - case K: consonant = "k"; break; - case H: consonant = "h"; break; - case J: consonant = "j"; if (hasNoMVCOrVC) middle = "i"; break; - case Q: consonant = "q"; if (hasNoMVCOrVC) middle = "i"; break; - case X: consonant = "x"; if (hasNoMVCOrVC) middle = "i"; break; - case ZH: consonant = "zh"; if (hasNoMVCOrVC) middle = "i"; break; - case CH: consonant = "ch"; if (hasNoMVCOrVC) middle = "i"; break; - case SH: consonant = "sh"; if (hasNoMVCOrVC) middle = "i"; break; - case R: consonant = "r"; if (hasNoMVCOrVC) middle = "i"; break; - case Z: consonant = "z"; if (hasNoMVCOrVC) middle = "i"; break; - case C: consonant = "c"; if (hasNoMVCOrVC) middle = "i"; break; - case S: consonant = "s"; if (hasNoMVCOrVC) middle = "i"; break; + case B: + consonant = "b"; + break; + case P: + consonant = "p"; + break; + case M: + consonant = "m"; + break; + case F: + consonant = "f"; + break; + case D: + consonant = "d"; + break; + case T: + consonant = "t"; + break; + case N: + consonant = "n"; + break; + case L: + consonant = "l"; + break; + case G: + consonant = "g"; + break; + case K: + consonant = "k"; + break; + case H: + consonant = "h"; + break; + case J: + consonant = "j"; + if (hasNoMVCOrVC) middle = "i"; + break; + case Q: + consonant = "q"; + if (hasNoMVCOrVC) middle = "i"; + break; + case X: + consonant = "x"; + if (hasNoMVCOrVC) middle = "i"; + break; + case ZH: + consonant = "zh"; + if (hasNoMVCOrVC) middle = "i"; + break; + case CH: + consonant = "ch"; + if (hasNoMVCOrVC) middle = "i"; + break; + case SH: + consonant = "sh"; + if (hasNoMVCOrVC) middle = "i"; + break; + case R: + consonant = "r"; + if (hasNoMVCOrVC) middle = "i"; + break; + case Z: + consonant = "z"; + if (hasNoMVCOrVC) middle = "i"; + break; + case C: + consonant = "c"; + if (hasNoMVCOrVC) middle = "i"; + break; + case S: + consonant = "s"; + if (hasNoMVCOrVC) middle = "i"; + break; } switch (mvc) { case I: if (!cc) { consonant = "y"; - } middle = (!vc || cc) ? "i" : ""; @@ -260,16 +416,15 @@ const string BPMF::HanyuPinyinString(bool includesTone, bool useVForUUmlaut) con consonant = "w"; } middle = (!vc || cc) ? "u" : ""; - break; + break; case UE: if (!cc) { consonant = "y"; } - + if ((cc == N || cc == L) && vc != E) { middle = useVForUUmlaut ? "v" : "ü"; - } - else { + } else { middle = "u"; } @@ -277,47 +432,72 @@ const string BPMF::HanyuPinyinString(bool includesTone, bool useVForUUmlaut) con } switch (vc) { - case A: vowel = "a"; break; - case O: vowel = "o"; break; - case ER: vowel = "e"; break; - case E: vowel = "e"; break; - case AI: vowel = "ai"; break; - case EI: vowel = "ei"; break; - case AO: vowel = "ao"; break; - case OU: vowel = "ou"; break; - case AN: vowel = "an"; break; - case EN: vowel = "en"; break; - case ANG: vowel = "ang"; break; - case ENG: vowel = "eng"; break; - case ERR: vowel = "er"; break; + case A: + vowel = "a"; + break; + case O: + vowel = "o"; + break; + case ER: + vowel = "e"; + break; + case E: + vowel = "e"; + break; + case AI: + vowel = "ai"; + break; + case EI: + vowel = "ei"; + break; + case AO: + vowel = "ao"; + break; + case OU: + vowel = "ou"; + break; + case AN: + vowel = "an"; + break; + case EN: + vowel = "en"; + break; + case ANG: + vowel = "ang"; + break; + case ENG: + vowel = "eng"; + break; + case ERR: + vowel = "er"; + break; } - + // combination rules // ueng -> ong, but note "weng" - if ((mvc == U || mvc == UE) && vc == ENG) { + if ((mvc == U || mvc == UE) && vc == ENG) { middle = ""; - vowel = (cc == J || cc == Q || cc == X) ? "iong" : ((!cc && mvc == U) ? "eng" : "ong"); + vowel = (cc == J || cc == Q || cc == X) + ? "iong" + : ((!cc && mvc == U) ? "eng" : "ong"); } - + // ien, uen, üen -> in, un, ün ; but note "wen", "yin" and "yun" if (mvc && vc == EN) { if (cc) { vowel = "n"; - } - else { + } else { if (mvc == UE) { - vowel = "n"; // yun - } - else if (mvc == U) { - vowel = "en"; // wen - } - else { - vowel = "in"; // yin + vowel = "n"; // yun + } else if (mvc == U) { + vowel = "en"; // wen + } else { + vowel = "in"; // yin } } } - + // iou -> iu if (cc && mvc == I && vc == OU) { middle = ""; @@ -335,52 +515,111 @@ const string BPMF::HanyuPinyinString(bool includesTone, bool useVForUUmlaut) con middle = ""; vowel = "ui"; } - if (includesTone) { switch (toneMarkerComponent()) { - case Tone1: tone = "1"; break; - case Tone2: tone = "2"; break; - case Tone3: tone = "3"; break; - case Tone4: tone = "4"; break; - case Tone5: tone = "5"; break; + case Tone1: + tone = "1"; + break; + case Tone2: + tone = "2"; + break; + case Tone3: + tone = "3"; + break; + case Tone4: + tone = "4"; + break; + case Tone5: + tone = "5"; + break; } } return consonant + middle + vowel + tone; } - - -const string BPMF::PHTString(bool includesTone) const -{ - string consonant, middle, vowel, tone; +const std::string BPMF::PHTString(bool includesTone) const { + std::string consonant, middle, vowel, tone; - Component cc = consonantComponent(), mvc = middleVowelComponent(), vc = vowelComponent(); - bool hasNoMVCOrVC = !(mvc || vc); + Component cc = consonantComponent(), mvc = middleVowelComponent(), + vc = vowelComponent(); + bool hasNoMVCOrVC = !(mvc || vc); switch (cc) { - case B: consonant = "p"; break; - case P: consonant = "ph"; break; - case M: consonant = "m"; break; - case F: consonant = "f"; break; - case D: consonant = "t"; break; - case T: consonant = "th"; break; - case N: consonant = "n"; break; - case L: consonant = "l"; break; - case G: consonant = "k"; break; - case K: consonant = "kh"; break; - case H: consonant = "h"; break; - case J: consonant = "ch"; if (mvc != I) middle = "i"; break; - case Q: consonant = "chh"; if (mvc != I) middle = "i"; break; - case X: consonant = "hs"; if (mvc != I) middle = "i"; break; - case ZH: consonant = "ch"; if (hasNoMVCOrVC) middle = "i"; break; - case CH: consonant = "chh"; if (hasNoMVCOrVC) middle = "i"; break; - case SH: consonant = "sh"; if (hasNoMVCOrVC) middle = "i"; break; - case R: consonant = "r"; if (hasNoMVCOrVC) middle = "i"; break; - case Z: consonant = "ts"; if (hasNoMVCOrVC) middle = "i"; break; - case C: consonant = "tsh"; if (hasNoMVCOrVC) middle = "i"; break; - case S: consonant = "s"; if (hasNoMVCOrVC) middle = "i"; break; + case B: + consonant = "p"; + break; + case P: + consonant = "ph"; + break; + case M: + consonant = "m"; + break; + case F: + consonant = "f"; + break; + case D: + consonant = "t"; + break; + case T: + consonant = "th"; + break; + case N: + consonant = "n"; + break; + case L: + consonant = "l"; + break; + case G: + consonant = "k"; + break; + case K: + consonant = "kh"; + break; + case H: + consonant = "h"; + break; + case J: + consonant = "ch"; + if (mvc != I) middle = "i"; + break; + case Q: + consonant = "chh"; + if (mvc != I) middle = "i"; + break; + case X: + consonant = "hs"; + if (mvc != I) middle = "i"; + break; + case ZH: + consonant = "ch"; + if (hasNoMVCOrVC) middle = "i"; + break; + case CH: + consonant = "chh"; + if (hasNoMVCOrVC) middle = "i"; + break; + case SH: + consonant = "sh"; + if (hasNoMVCOrVC) middle = "i"; + break; + case R: + consonant = "r"; + if (hasNoMVCOrVC) middle = "i"; + break; + case Z: + consonant = "ts"; + if (hasNoMVCOrVC) middle = "i"; + break; + case C: + consonant = "tsh"; + if (hasNoMVCOrVC) middle = "i"; + break; + case S: + consonant = "s"; + if (hasNoMVCOrVC) middle = "i"; + break; } switch (mvc) { @@ -389,28 +628,54 @@ const string BPMF::PHTString(bool includesTone) const break; case U: middle = "u"; - break; + break; case UE: middle = "uu"; break; } switch (vc) { - case A: vowel = "a"; break; - case O: vowel = "o"; break; - case ER: vowel = "e"; break; - case E: vowel = (!(cc || mvc)) ? "eh" : "e"; break; - case AI: vowel = "ai"; break; - case EI: vowel = "ei"; break; - case AO: vowel = "ao"; break; - case OU: vowel = "ou"; break; - case AN: vowel = "an"; break; - case EN: vowel = "en"; break; - case ANG: vowel = "ang"; break; - case ENG: vowel = "eng"; break; - case ERR: vowel = "err"; break; + case A: + vowel = "a"; + break; + case O: + vowel = "o"; + break; + case ER: + vowel = "e"; + break; + case E: + vowel = (!(cc || mvc)) ? "eh" : "e"; + break; + case AI: + vowel = "ai"; + break; + case EI: + vowel = "ei"; + break; + case AO: + vowel = "ao"; + break; + case OU: + vowel = "ou"; + break; + case AN: + vowel = "an"; + break; + case EN: + vowel = "en"; + break; + case ANG: + vowel = "ang"; + break; + case ENG: + vowel = "eng"; + break; + case ERR: + vowel = "err"; + break; } - + // ieng -> ing if (mvc == I && vc == ENG) { middle = ""; @@ -424,38 +689,50 @@ const string BPMF::PHTString(bool includesTone) const } } - if (includesTone) { - switch (toneMarkerComponent()) { - case Tone2: tone = "2"; break; - case Tone3: tone = "3"; break; - case Tone4: tone = "4"; break; - case Tone5: tone = "5"; break; + switch (toneMarkerComponent()) { + case Tone1: + tone = "1"; + break; + case Tone2: + tone = "2"; + break; + case Tone3: + tone = "3"; + break; + case Tone4: + tone = "4"; + break; + case Tone5: + tone = "5"; + break; } } return consonant + middle + vowel + tone; - } -const BPMF BPMF::FromPHT(const string& str) -{ +const BPMF BPMF::FromPHT(const std::string& str) { if (!str.length()) { return BPMF(); } - string pht = str; + std::string pht = str; transform(pht.begin(), pht.end(), pht.begin(), ::tolower); - + BPMF::Component firstComponent = 0; BPMF::Component secondComponent = 0; BPMF::Component thirdComponent = 0; BPMF::Component toneComponent = 0; - - #define IF_CONSUME1(k, v) else if (PinyinParseHelper::ConsumePrefix(pht, k)) { firstComponent = v; } + +#define IF_CONSUME1(k, v) \ +else if (PinyinParseHelper::ConsumePrefix(pht, k)) { \ +firstComponent = v; \ +} // consume the first part - if (0) {} + if (0) { + } IF_CONSUME1("ph", BPMF::P) IF_CONSUME1("p", BPMF::B) IF_CONSUME1("m", BPMF::M) @@ -476,15 +753,19 @@ const BPMF BPMF::FromPHT(const string& str) IF_CONSUME1("t", BPMF::D) IF_CONSUME1("h", BPMF::H) - #define IF_CONSUME2(k, v) else if (PinyinParseHelper::ConsumePrefix(pht, k)) { secondComponent = v; } +#define IF_CONSUME2(k, v) \ +else if (PinyinParseHelper::ConsumePrefix(pht, k)) { \ +secondComponent = v; \ +} // consume the second part - if (0) {} - else if (PinyinParseHelper::ConsumePrefix(pht, "ing")) { secondComponent = BPMF::I; thirdComponent = BPMF::ENG; } - else if (PinyinParseHelper::ConsumePrefix(pht, "ih")) { + if (0) { + } else if (PinyinParseHelper::ConsumePrefix(pht, "ing")) { + secondComponent = BPMF::I; + thirdComponent = BPMF::ENG; + } else if (PinyinParseHelper::ConsumePrefix(pht, "ih")) { if (firstComponent == BPMF::J) { firstComponent = BPMF::ZH; - } - else if (firstComponent == BPMF::Q) { + } else if (firstComponent == BPMF::Q) { firstComponent = BPMF::CH; } } @@ -492,52 +773,61 @@ const BPMF BPMF::FromPHT(const string& str) IF_CONSUME2("uu", BPMF::UE) IF_CONSUME2("u", BPMF::U) - #undef IF_CONSUME1 - #undef IF_CONSUME2 +#undef IF_CONSUME1 +#undef IF_CONSUME2 // the vowels, longer sequence takes precedence - if (0) {} - else if (PinyinParseHelper::ConsumePrefix(pht, "ang")) { thirdComponent = BPMF::ANG; } - else if (PinyinParseHelper::ConsumePrefix(pht, "eng")) { thirdComponent = BPMF::ENG; } - else if (PinyinParseHelper::ConsumePrefix(pht, "err")) { thirdComponent = BPMF::ERR; } - else if (PinyinParseHelper::ConsumePrefix(pht, "ai")) { thirdComponent = BPMF::AI; } - else if (PinyinParseHelper::ConsumePrefix(pht, "ei")) { thirdComponent = BPMF::EI; } - else if (PinyinParseHelper::ConsumePrefix(pht, "ao")) { thirdComponent = BPMF::AO; } - else if (PinyinParseHelper::ConsumePrefix(pht, "ou")) { thirdComponent = BPMF::OU; } - else if (PinyinParseHelper::ConsumePrefix(pht, "an")) { thirdComponent = BPMF::AN; } - else if (PinyinParseHelper::ConsumePrefix(pht, "en")) { thirdComponent = BPMF::EN; } - else if (PinyinParseHelper::ConsumePrefix(pht, "er")) { thirdComponent = BPMF::ERR; } - else if (PinyinParseHelper::ConsumePrefix(pht, "a")) { thirdComponent = BPMF::A; } - else if (PinyinParseHelper::ConsumePrefix(pht, "o")) { thirdComponent = BPMF::O; } - else if (PinyinParseHelper::ConsumePrefix(pht, "eh")) { thirdComponent = BPMF::E; } - else if (PinyinParseHelper::ConsumePrefix(pht, "e")) { + if (0) { + } else if (PinyinParseHelper::ConsumePrefix(pht, "ang")) { + thirdComponent = BPMF::ANG; + } else if (PinyinParseHelper::ConsumePrefix(pht, "eng")) { + thirdComponent = BPMF::ENG; + } else if (PinyinParseHelper::ConsumePrefix(pht, "err")) { + thirdComponent = BPMF::ERR; + } else if (PinyinParseHelper::ConsumePrefix(pht, "ai")) { + thirdComponent = BPMF::AI; + } else if (PinyinParseHelper::ConsumePrefix(pht, "ei")) { + thirdComponent = BPMF::EI; + } else if (PinyinParseHelper::ConsumePrefix(pht, "ao")) { + thirdComponent = BPMF::AO; + } else if (PinyinParseHelper::ConsumePrefix(pht, "ou")) { + thirdComponent = BPMF::OU; + } else if (PinyinParseHelper::ConsumePrefix(pht, "an")) { + thirdComponent = BPMF::AN; + } else if (PinyinParseHelper::ConsumePrefix(pht, "en")) { + thirdComponent = BPMF::EN; + } else if (PinyinParseHelper::ConsumePrefix(pht, "er")) { + thirdComponent = BPMF::ERR; + } else if (PinyinParseHelper::ConsumePrefix(pht, "a")) { + thirdComponent = BPMF::A; + } else if (PinyinParseHelper::ConsumePrefix(pht, "o")) { + thirdComponent = BPMF::O; + } else if (PinyinParseHelper::ConsumePrefix(pht, "eh")) { + thirdComponent = BPMF::E; + } else if (PinyinParseHelper::ConsumePrefix(pht, "e")) { if (secondComponent) { thirdComponent = BPMF::E; - } - else { + } else { thirdComponent = BPMF::ER; } } - + // fix ch/chh mappings Component corresponding = 0; if (firstComponent == BPMF::J) { corresponding = BPMF::ZH; - } - else if (firstComponent == BPMF::Q) { + } else if (firstComponent == BPMF::Q) { corresponding = BPMF::CH; } if (corresponding) { if (secondComponent == BPMF::I && !thirdComponent) { - // if the second component is I and there's no third component, we use the corresponding part - // firstComponent = corresponding; - } - else if (secondComponent == BPMF::U) { + // if the second component is I and there's no third component, we use the + // corresponding part firstComponent = corresponding; + } else if (secondComponent == BPMF::U) { // if second component is U, we use the corresponding part firstComponent = corresponding; - } - else if (!secondComponent) { + } else if (!secondComponent) { // if there's no second component, it must be a corresponding part firstComponent = corresponding; } @@ -545,7 +835,7 @@ const BPMF BPMF::FromPHT(const string& str) if (secondComponent == BPMF::I) { // fixes a few impossible occurances - switch(firstComponent) { + switch (firstComponent) { case BPMF::ZH: case BPMF::CH: case BPMF::SH: @@ -557,438 +847,396 @@ const BPMF BPMF::FromPHT(const string& str) } } - // at last! - if (0) {} - else if (PinyinParseHelper::ConsumePrefix(pht, "1")) { toneComponent = BPMF::Tone1; } - else if (PinyinParseHelper::ConsumePrefix(pht, "2")) { toneComponent = BPMF::Tone2; } - else if (PinyinParseHelper::ConsumePrefix(pht, "3")) { toneComponent = BPMF::Tone3; } - else if (PinyinParseHelper::ConsumePrefix(pht, "4")) { toneComponent = BPMF::Tone4; } - else if (PinyinParseHelper::ConsumePrefix(pht, "5")) { toneComponent = BPMF::Tone5; } - - return BPMF(firstComponent | secondComponent | thirdComponent | toneComponent); + if (0) { + } else if (PinyinParseHelper::ConsumePrefix(pht, "1")) { + toneComponent = BPMF::Tone1; + } else if (PinyinParseHelper::ConsumePrefix(pht, "2")) { + toneComponent = BPMF::Tone2; + } else if (PinyinParseHelper::ConsumePrefix(pht, "3")) { + toneComponent = BPMF::Tone3; + } else if (PinyinParseHelper::ConsumePrefix(pht, "4")) { + toneComponent = BPMF::Tone4; + } else if (PinyinParseHelper::ConsumePrefix(pht, "5")) { + toneComponent = BPMF::Tone5; + } + + return BPMF(firstComponent | secondComponent | thirdComponent | + toneComponent); } -const BPMF BPMF::FromComposedString(const string& str) -{ +const BPMF BPMF::FromComposedString(const std::string& str) { BPMF syllable; - vector components = OVUTF8Helper::SplitStringByCodePoint(str); - for (vector::iterator iter = components.begin() ; iter != components.end() ; ++iter) { - - const map& charToComp = BopomofoCharacterMap::SharedInstance().characterToComponent; - map::const_iterator result = charToComp.find(*iter); - if (result != charToComp.end()) - syllable += BPMF((*result).second); + auto iter = str.begin(); + while (iter != str.end()) { + // This is a naive implementation and we bail early at anything we don't + // recognize. A sound implementation would require to either use a trie for + // the Bopomofo character map or to split the input by codepoints. This + // suffices for now. + + // Illegal. + if (!(*iter & 0x80)) { + break; + } + + size_t utf8_length = -1; + + // These are the code points for the tone markers. + if ((*iter & (0x80 | 0x40)) && !(*iter & 0x20)) { + utf8_length = 2; + } else if ((*iter & (0x80 | 0x40 | 0x20)) && !(*iter & 0x10)) { + utf8_length = 3; + } else { + // Illegal. + break; + } + + if (iter + (utf8_length - 1) == str.end()) { + break; + } + + std::string component = std::string(iter, iter + utf8_length); + const std::map& charToComp = + BopomofoCharacterMap::SharedInstance().characterToComponent; + std::map::const_iterator result = + charToComp.find(component); + if (result == charToComp.end()) { + break; + } else { + syllable += BPMF((*result).second); + } + iter += utf8_length; } - return syllable; } -const string BPMF::composedString() const -{ - string result; - #define APPEND(c) if (m_syllable & c) result += (*BopomofoCharacterMap::SharedInstance().componentToCharacter.find(m_syllable & c)).second +const std::string BPMF::composedString() const { + std::string result; +#define APPEND(c) \ +if (syllable_ & c) \ +result += \ +(*BopomofoCharacterMap::SharedInstance().componentToCharacter.find( \ +syllable_ & c)) \ +.second APPEND(ConsonantMask); APPEND(MiddleVowelMask); APPEND(VowelMask); APPEND(ToneMarkerMask); - #undef APPEND +#undef APPEND return result; } -BopomofoCharacterMap* BopomofoCharacterMap::c_map = 0; - -const BopomofoCharacterMap& BopomofoCharacterMap::SharedInstance() -{ - if (!c_map) - c_map = new BopomofoCharacterMap(); - - return *c_map; +const BopomofoCharacterMap& BopomofoCharacterMap::SharedInstance() { + static BopomofoCharacterMap* map = new BopomofoCharacterMap(); + return *map; } -BopomofoCharacterMap::BopomofoCharacterMap() -{ -#ifndef _MSC_VER - characterToComponent["ㄅ"] = BPMF::B; - characterToComponent["ㄆ"] = BPMF::P; - characterToComponent["ㄇ"] = BPMF::M; - characterToComponent["ㄈ"] = BPMF::F; - characterToComponent["ㄉ"] = BPMF::D; - characterToComponent["ㄊ"] = BPMF::T; - characterToComponent["ㄋ"] = BPMF::N; - characterToComponent["ㄌ"] = BPMF::L; - characterToComponent["ㄎ"] = BPMF::K; - characterToComponent["ㄍ"] = BPMF::G; - characterToComponent["ㄏ"] = BPMF::H; - characterToComponent["ㄐ"] = BPMF::J; - characterToComponent["ㄑ"] = BPMF::Q; - characterToComponent["ㄒ"] = BPMF::X; - characterToComponent["ㄓ"] = BPMF::ZH; - characterToComponent["ㄔ"] = BPMF::CH; - characterToComponent["ㄕ"] = BPMF::SH; - characterToComponent["ㄖ"] = BPMF::R; - characterToComponent["ㄗ"] = BPMF::Z; - characterToComponent["ㄘ"] = BPMF::C; - characterToComponent["ㄙ"] = BPMF::S; - characterToComponent["ㄧ"] = BPMF::I; - characterToComponent["ㄨ"] = BPMF::U; - characterToComponent["ㄩ"] = BPMF::UE; - characterToComponent["ㄚ"] = BPMF::A; - characterToComponent["ㄛ"] = BPMF::O; - characterToComponent["ㄜ"] = BPMF::ER; - characterToComponent["ㄝ"] = BPMF::E; - characterToComponent["ㄞ"] = BPMF::AI; - characterToComponent["ㄟ"] = BPMF::EI; - characterToComponent["ㄠ"] = BPMF::AO; - characterToComponent["ㄡ"] = BPMF::OU; - characterToComponent["ㄢ"] = BPMF::AN; - characterToComponent["ㄣ"] = BPMF::EN; - characterToComponent["ㄤ"] = BPMF::ANG; - characterToComponent["ㄥ"] = BPMF::ENG; - characterToComponent["ㄦ"] = BPMF::ERR; - characterToComponent["ˊ"] = BPMF::Tone2; - characterToComponent["ˇ"] = BPMF::Tone3; - characterToComponent["ˋ"] = BPMF::Tone4; - characterToComponent["˙"] = BPMF::Tone5; -#else - characterToComponent["\xe3\x84\x85"] = BPMF::B; - characterToComponent["\xe3\x84\x86"] = BPMF::P; - characterToComponent["\xe3\x84\x87"] = BPMF::M; - characterToComponent["\xe3\x84\x88"] = BPMF::F; - characterToComponent["\xe3\x84\x89"] = BPMF::D; - characterToComponent["\xe3\x84\x8a"] = BPMF::T; - characterToComponent["\xe3\x84\x8b"] = BPMF::N; - characterToComponent["\xe3\x84\x8c"] = BPMF::L; - characterToComponent["\xe3\x84\x8e"] = BPMF::K; - characterToComponent["\xe3\x84\x8d"] = BPMF::G; - characterToComponent["\xe3\x84\x8f"] = BPMF::H; - characterToComponent["\xe3\x84\x90"] = BPMF::J; - characterToComponent["\xe3\x84\x91"] = BPMF::Q; - characterToComponent["\xe3\x84\x92"] = BPMF::X; - characterToComponent["\xe3\x84\x93"] = BPMF::ZH; - characterToComponent["\xe3\x84\x94"] = BPMF::CH; - characterToComponent["\xe3\x84\x95"] = BPMF::SH; - characterToComponent["\xe3\x84\x96"] = BPMF::R; - characterToComponent["\xe3\x84\x97"] = BPMF::Z; - characterToComponent["\xe3\x84\x98"] = BPMF::C; - characterToComponent["\xe3\x84\x99"] = BPMF::S; - characterToComponent["\xe3\x84\xa7"] = BPMF::I; - characterToComponent["\xe3\x84\xa8"] = BPMF::U; - characterToComponent["\xe3\x84\xa9"] = BPMF::UE; - characterToComponent["\xe3\x84\x9a"] = BPMF::A; - characterToComponent["\xe3\x84\x9b"] = BPMF::O; - characterToComponent["\xe3\x84\x9c"] = BPMF::ER; - characterToComponent["\xe3\x84\x9d"] = BPMF::E; - characterToComponent["\xe3\x84\x9e"] = BPMF::AI; - characterToComponent["\xe3\x84\x9f"] = BPMF::EI; - characterToComponent["\xe3\x84\xa0"] = BPMF::AO; - characterToComponent["\xe3\x84\xa1"] = BPMF::OU; - characterToComponent["\xe3\x84\xa2"] = BPMF::AN; - characterToComponent["\xe3\x84\xa3"] = BPMF::EN; - characterToComponent["\xe3\x84\xa4"] = BPMF::ANG; - characterToComponent["\xe3\x84\xa5"] = BPMF::ENG; - characterToComponent["\xe3\x84\xa6"] = BPMF::ERR; - characterToComponent["\xcb\x8a"] = BPMF::Tone2; - characterToComponent["\xcb\x87"] = BPMF::Tone3; - characterToComponent["\xcb\x8b"] = BPMF::Tone4; - characterToComponent["\xcb\x99"] = BPMF::Tone5; -#endif - - for (map::iterator iter = characterToComponent.begin() ; iter != characterToComponent.end() ; ++iter) +BopomofoCharacterMap::BopomofoCharacterMap() { + characterToComponent[u8"ㄅ"] = BPMF::B; + characterToComponent[u8"ㄆ"] = BPMF::P; + characterToComponent[u8"ㄇ"] = BPMF::M; + characterToComponent[u8"ㄈ"] = BPMF::F; + characterToComponent[u8"ㄉ"] = BPMF::D; + characterToComponent[u8"ㄊ"] = BPMF::T; + characterToComponent[u8"ㄋ"] = BPMF::N; + characterToComponent[u8"ㄌ"] = BPMF::L; + characterToComponent[u8"ㄎ"] = BPMF::K; + characterToComponent[u8"ㄍ"] = BPMF::G; + characterToComponent[u8"ㄏ"] = BPMF::H; + characterToComponent[u8"ㄐ"] = BPMF::J; + characterToComponent[u8"ㄑ"] = BPMF::Q; + characterToComponent[u8"ㄒ"] = BPMF::X; + characterToComponent[u8"ㄓ"] = BPMF::ZH; + characterToComponent[u8"ㄔ"] = BPMF::CH; + characterToComponent[u8"ㄕ"] = BPMF::SH; + characterToComponent[u8"ㄖ"] = BPMF::R; + characterToComponent[u8"ㄗ"] = BPMF::Z; + characterToComponent[u8"ㄘ"] = BPMF::C; + characterToComponent[u8"ㄙ"] = BPMF::S; + characterToComponent[u8"ㄧ"] = BPMF::I; + characterToComponent[u8"ㄨ"] = BPMF::U; + characterToComponent[u8"ㄩ"] = BPMF::UE; + characterToComponent[u8"ㄚ"] = BPMF::A; + characterToComponent[u8"ㄛ"] = BPMF::O; + characterToComponent[u8"ㄜ"] = BPMF::ER; + characterToComponent[u8"ㄝ"] = BPMF::E; + characterToComponent[u8"ㄞ"] = BPMF::AI; + characterToComponent[u8"ㄟ"] = BPMF::EI; + characterToComponent[u8"ㄠ"] = BPMF::AO; + characterToComponent[u8"ㄡ"] = BPMF::OU; + characterToComponent[u8"ㄢ"] = BPMF::AN; + characterToComponent[u8"ㄣ"] = BPMF::EN; + characterToComponent[u8"ㄤ"] = BPMF::ANG; + characterToComponent[u8"ㄥ"] = BPMF::ENG; + characterToComponent[u8"ㄦ"] = BPMF::ERR; + characterToComponent[u8"ˊ"] = BPMF::Tone2; + characterToComponent[u8"ˇ"] = BPMF::Tone3; + characterToComponent[u8"ˋ"] = BPMF::Tone4; + characterToComponent[u8"˙"] = BPMF::Tone5; + + for (std::map::iterator iter = + characterToComponent.begin(); + iter != characterToComponent.end(); ++iter) componentToCharacter[(*iter).second] = (*iter).first; } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::c_StandardLayout = 0; -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::c_ETenLayout = 0; -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::c_HsuLayout = 0; -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::c_ETen26Layout = 0; -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::c_IBMLayout = 0; -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::c_HanyuPinyinLayout = 0; +#define ASSIGNKEY1(m, vec, k, val) \ +m[k] = (vec.clear(), vec.push_back((BPMF::Component)val), vec) +#define ASSIGNKEY2(m, vec, k, val1, val2) \ +m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), \ +vec.push_back((BPMF::Component)val2), vec) +#define ASSIGNKEY3(m, vec, k, val1, val2, val3) \ +m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), \ +vec.push_back((BPMF::Component)val2), \ +vec.push_back((BPMF::Component)val3), vec) -void BopomofoKeyboardLayout::FinalizeLayouts() -{ - #define FL(x) if (x) { delete x; } x = 0 - FL(c_StandardLayout); - FL(c_ETenLayout); - FL(c_HsuLayout); - FL(c_ETen26Layout); - FL(c_IBMLayout); - FL(c_HanyuPinyinLayout); - #undef FL +static BopomofoKeyboardLayout* CreateStandardLayout() { + std::vector vec; + BopomofoKeyToComponentMap ktcm; + + ASSIGNKEY1(ktcm, vec, '1', BPMF::B); + ASSIGNKEY1(ktcm, vec, 'q', BPMF::P); + ASSIGNKEY1(ktcm, vec, 'a', BPMF::M); + ASSIGNKEY1(ktcm, vec, 'z', BPMF::F); + ASSIGNKEY1(ktcm, vec, '2', BPMF::D); + ASSIGNKEY1(ktcm, vec, 'w', BPMF::T); + ASSIGNKEY1(ktcm, vec, 's', BPMF::N); + ASSIGNKEY1(ktcm, vec, 'x', BPMF::L); + ASSIGNKEY1(ktcm, vec, 'e', BPMF::G); + ASSIGNKEY1(ktcm, vec, 'd', BPMF::K); + ASSIGNKEY1(ktcm, vec, 'c', BPMF::H); + ASSIGNKEY1(ktcm, vec, 'r', BPMF::J); + ASSIGNKEY1(ktcm, vec, 'f', BPMF::Q); + ASSIGNKEY1(ktcm, vec, 'v', BPMF::X); + ASSIGNKEY1(ktcm, vec, '5', BPMF::ZH); + ASSIGNKEY1(ktcm, vec, 't', BPMF::CH); + ASSIGNKEY1(ktcm, vec, 'g', BPMF::SH); + ASSIGNKEY1(ktcm, vec, 'b', BPMF::R); + ASSIGNKEY1(ktcm, vec, 'y', BPMF::Z); + ASSIGNKEY1(ktcm, vec, 'h', BPMF::C); + ASSIGNKEY1(ktcm, vec, 'n', BPMF::S); + ASSIGNKEY1(ktcm, vec, 'u', BPMF::I); + ASSIGNKEY1(ktcm, vec, 'j', BPMF::U); + ASSIGNKEY1(ktcm, vec, 'm', BPMF::UE); + ASSIGNKEY1(ktcm, vec, '8', BPMF::A); + ASSIGNKEY1(ktcm, vec, 'i', BPMF::O); + ASSIGNKEY1(ktcm, vec, 'k', BPMF::ER); + ASSIGNKEY1(ktcm, vec, ',', BPMF::E); + ASSIGNKEY1(ktcm, vec, '9', BPMF::AI); + ASSIGNKEY1(ktcm, vec, 'o', BPMF::EI); + ASSIGNKEY1(ktcm, vec, 'l', BPMF::AO); + ASSIGNKEY1(ktcm, vec, '.', BPMF::OU); + ASSIGNKEY1(ktcm, vec, '0', BPMF::AN); + ASSIGNKEY1(ktcm, vec, 'p', BPMF::EN); + ASSIGNKEY1(ktcm, vec, ';', BPMF::ANG); + ASSIGNKEY1(ktcm, vec, '/', BPMF::ENG); + ASSIGNKEY1(ktcm, vec, '-', BPMF::ERR); + ASSIGNKEY1(ktcm, vec, '3', BPMF::Tone3); + ASSIGNKEY1(ktcm, vec, '4', BPMF::Tone4); + ASSIGNKEY1(ktcm, vec, '6', BPMF::Tone2); + ASSIGNKEY1(ktcm, vec, '7', BPMF::Tone5); + + return new BopomofoKeyboardLayout(ktcm, "Standard"); } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::LayoutForName(const string& name) -{ - if (OVWildcard::Match(name, "standard")) - return StandardLayout(); - - if (OVWildcard::Match(name, "eten")) - return ETenLayout(); - - if (OVWildcard::Match(name, "hsu")) - return HsuLayout(); +static BopomofoKeyboardLayout* CreateIBMLayout() { + std::vector vec; + BopomofoKeyToComponentMap ktcm; - if (OVWildcard::Match(name, "eten26")) - return ETen26Layout(); - - if (OVWildcard::Match(name, "IBM")) - return IBMLayout(); - - if (OVWildcard::Match(name, "hanyupinyin") || OVWildcard::Match(name, "hanyu pinyin") || OVWildcard::Match(name, "hanyu-pinyin") || OVWildcard::Match(name, "pinyin")) - return HanyuPinyinLayout(); - - return 0; + ASSIGNKEY1(ktcm, vec, '1', BPMF::B); + ASSIGNKEY1(ktcm, vec, '2', BPMF::P); + ASSIGNKEY1(ktcm, vec, '3', BPMF::M); + ASSIGNKEY1(ktcm, vec, '4', BPMF::F); + ASSIGNKEY1(ktcm, vec, '5', BPMF::D); + ASSIGNKEY1(ktcm, vec, '6', BPMF::T); + ASSIGNKEY1(ktcm, vec, '7', BPMF::N); + ASSIGNKEY1(ktcm, vec, '8', BPMF::L); + ASSIGNKEY1(ktcm, vec, '9', BPMF::G); + ASSIGNKEY1(ktcm, vec, '0', BPMF::K); + ASSIGNKEY1(ktcm, vec, '-', BPMF::H); + ASSIGNKEY1(ktcm, vec, 'q', BPMF::J); + ASSIGNKEY1(ktcm, vec, 'w', BPMF::Q); + ASSIGNKEY1(ktcm, vec, 'e', BPMF::X); + ASSIGNKEY1(ktcm, vec, 'r', BPMF::ZH); + ASSIGNKEY1(ktcm, vec, 't', BPMF::CH); + ASSIGNKEY1(ktcm, vec, 'y', BPMF::SH); + ASSIGNKEY1(ktcm, vec, 'u', BPMF::R); + ASSIGNKEY1(ktcm, vec, 'i', BPMF::Z); + ASSIGNKEY1(ktcm, vec, 'o', BPMF::C); + ASSIGNKEY1(ktcm, vec, 'p', BPMF::S); + ASSIGNKEY1(ktcm, vec, 'a', BPMF::I); + ASSIGNKEY1(ktcm, vec, 's', BPMF::U); + ASSIGNKEY1(ktcm, vec, 'd', BPMF::UE); + ASSIGNKEY1(ktcm, vec, 'f', BPMF::A); + ASSIGNKEY1(ktcm, vec, 'g', BPMF::O); + ASSIGNKEY1(ktcm, vec, 'h', BPMF::ER); + ASSIGNKEY1(ktcm, vec, 'j', BPMF::E); + ASSIGNKEY1(ktcm, vec, 'k', BPMF::AI); + ASSIGNKEY1(ktcm, vec, 'l', BPMF::EI); + ASSIGNKEY1(ktcm, vec, ';', BPMF::AO); + ASSIGNKEY1(ktcm, vec, 'z', BPMF::OU); + ASSIGNKEY1(ktcm, vec, 'x', BPMF::AN); + ASSIGNKEY1(ktcm, vec, 'c', BPMF::EN); + ASSIGNKEY1(ktcm, vec, 'v', BPMF::ANG); + ASSIGNKEY1(ktcm, vec, 'b', BPMF::ENG); + ASSIGNKEY1(ktcm, vec, 'n', BPMF::ERR); + ASSIGNKEY1(ktcm, vec, 'm', BPMF::Tone2); + ASSIGNKEY1(ktcm, vec, ',', BPMF::Tone3); + ASSIGNKEY1(ktcm, vec, '.', BPMF::Tone4); + ASSIGNKEY1(ktcm, vec, '/', BPMF::Tone5); + + return new BopomofoKeyboardLayout(ktcm, "IBM"); } -#define ASSIGNKEY1(m, vec, k, val) m[k] = (vec.clear(), vec.push_back((BPMF::Component)val), vec) -#define ASSIGNKEY2(m, vec, k, val1, val2) m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), vec.push_back((BPMF::Component)val2), vec) -#define ASSIGNKEY3(m, vec, k, val1, val2, val3) m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), vec.push_back((BPMF::Component)val2), vec.push_back((BPMF::Component)val3), vec) - -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::StandardLayout() -{ - if (!c_StandardLayout) { - vector vec; - BopomofoKeyToComponentMap ktcm; - - ASSIGNKEY1(ktcm, vec, '1', BPMF::B); - ASSIGNKEY1(ktcm, vec, 'q', BPMF::P); - ASSIGNKEY1(ktcm, vec, 'a', BPMF::M); - ASSIGNKEY1(ktcm, vec, 'z', BPMF::F); - ASSIGNKEY1(ktcm, vec, '2', BPMF::D); - ASSIGNKEY1(ktcm, vec, 'w', BPMF::T); - ASSIGNKEY1(ktcm, vec, 's', BPMF::N); - ASSIGNKEY1(ktcm, vec, 'x', BPMF::L); - ASSIGNKEY1(ktcm, vec, 'e', BPMF::G); - ASSIGNKEY1(ktcm, vec, 'd', BPMF::K); - ASSIGNKEY1(ktcm, vec, 'c', BPMF::H); - ASSIGNKEY1(ktcm, vec, 'r', BPMF::J); - ASSIGNKEY1(ktcm, vec, 'f', BPMF::Q); - ASSIGNKEY1(ktcm, vec, 'v', BPMF::X); - ASSIGNKEY1(ktcm, vec, '5', BPMF::ZH); - ASSIGNKEY1(ktcm, vec, 't', BPMF::CH); - ASSIGNKEY1(ktcm, vec, 'g', BPMF::SH); - ASSIGNKEY1(ktcm, vec, 'b', BPMF::R); - ASSIGNKEY1(ktcm, vec, 'y', BPMF::Z); - ASSIGNKEY1(ktcm, vec, 'h', BPMF::C); - ASSIGNKEY1(ktcm, vec, 'n', BPMF::S); - ASSIGNKEY1(ktcm, vec, 'u', BPMF::I); - ASSIGNKEY1(ktcm, vec, 'j', BPMF::U); - ASSIGNKEY1(ktcm, vec, 'm', BPMF::UE); - ASSIGNKEY1(ktcm, vec, '8', BPMF::A); - ASSIGNKEY1(ktcm, vec, 'i', BPMF::O); - ASSIGNKEY1(ktcm, vec, 'k', BPMF::ER); - ASSIGNKEY1(ktcm, vec, ',', BPMF::E); - ASSIGNKEY1(ktcm, vec, '9', BPMF::AI); - ASSIGNKEY1(ktcm, vec, 'o', BPMF::EI); - ASSIGNKEY1(ktcm, vec, 'l', BPMF::AO); - ASSIGNKEY1(ktcm, vec, '.', BPMF::OU); - ASSIGNKEY1(ktcm, vec, '0', BPMF::AN); - ASSIGNKEY1(ktcm, vec, 'p', BPMF::EN); - ASSIGNKEY1(ktcm, vec, ';', BPMF::ANG); - ASSIGNKEY1(ktcm, vec, '/', BPMF::ENG); - ASSIGNKEY1(ktcm, vec, '-', BPMF::ERR); - ASSIGNKEY1(ktcm, vec, '3', BPMF::Tone3); - ASSIGNKEY1(ktcm, vec, '4', BPMF::Tone4); - ASSIGNKEY1(ktcm, vec, '6', BPMF::Tone2); - ASSIGNKEY1(ktcm, vec, '7', BPMF::Tone5); - - c_StandardLayout = new BopomofoKeyboardLayout(ktcm, "Standard"); - } +static BopomofoKeyboardLayout* CreateETenLayout() { + std::vector vec; + BopomofoKeyToComponentMap ktcm; - return c_StandardLayout; -} -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::IBMLayout() -{ - if (!c_IBMLayout) { - vector vec; - BopomofoKeyToComponentMap ktcm; - - ASSIGNKEY1(ktcm, vec, '1', BPMF::B); - ASSIGNKEY1(ktcm, vec, '2', BPMF::P); - ASSIGNKEY1(ktcm, vec, '3', BPMF::M); - ASSIGNKEY1(ktcm, vec, '4', BPMF::F); - ASSIGNKEY1(ktcm, vec, '5', BPMF::D); - ASSIGNKEY1(ktcm, vec, '6', BPMF::T); - ASSIGNKEY1(ktcm, vec, '7', BPMF::N); - ASSIGNKEY1(ktcm, vec, '8', BPMF::L); - ASSIGNKEY1(ktcm, vec, '9', BPMF::G); - ASSIGNKEY1(ktcm, vec, '0', BPMF::K); - ASSIGNKEY1(ktcm, vec, '-', BPMF::H); - ASSIGNKEY1(ktcm, vec, 'q', BPMF::J); - ASSIGNKEY1(ktcm, vec, 'w', BPMF::Q); - ASSIGNKEY1(ktcm, vec, 'e', BPMF::X); - ASSIGNKEY1(ktcm, vec, 'r', BPMF::ZH); - ASSIGNKEY1(ktcm, vec, 't', BPMF::CH); - ASSIGNKEY1(ktcm, vec, 'y', BPMF::SH); - ASSIGNKEY1(ktcm, vec, 'u', BPMF::R); - ASSIGNKEY1(ktcm, vec, 'i', BPMF::Z); - ASSIGNKEY1(ktcm, vec, 'o', BPMF::C); - ASSIGNKEY1(ktcm, vec, 'p', BPMF::S); - ASSIGNKEY1(ktcm, vec, 'a', BPMF::I); - ASSIGNKEY1(ktcm, vec, 's', BPMF::U); - ASSIGNKEY1(ktcm, vec, 'd', BPMF::UE); - ASSIGNKEY1(ktcm, vec, 'f', BPMF::A); - ASSIGNKEY1(ktcm, vec, 'g', BPMF::O); - ASSIGNKEY1(ktcm, vec, 'h', BPMF::ER); - ASSIGNKEY1(ktcm, vec, 'j', BPMF::E); - ASSIGNKEY1(ktcm, vec, 'k', BPMF::AI); - ASSIGNKEY1(ktcm, vec, 'l', BPMF::EI); - ASSIGNKEY1(ktcm, vec, ';', BPMF::AO); - ASSIGNKEY1(ktcm, vec, 'z', BPMF::OU); - ASSIGNKEY1(ktcm, vec, 'x', BPMF::AN); - ASSIGNKEY1(ktcm, vec, 'c', BPMF::EN); - ASSIGNKEY1(ktcm, vec, 'v', BPMF::ANG); - ASSIGNKEY1(ktcm, vec, 'b', BPMF::ENG); - ASSIGNKEY1(ktcm, vec, 'n', BPMF::ERR); - ASSIGNKEY1(ktcm, vec, 'm', BPMF::Tone2); - ASSIGNKEY1(ktcm, vec, ',', BPMF::Tone3); - ASSIGNKEY1(ktcm, vec, '.', BPMF::Tone4); - ASSIGNKEY1(ktcm, vec, '/', BPMF::Tone5); - - c_IBMLayout = new BopomofoKeyboardLayout(ktcm, "IBM"); - } + ASSIGNKEY1(ktcm, vec, 'b', BPMF::B); + ASSIGNKEY1(ktcm, vec, 'p', BPMF::P); + ASSIGNKEY1(ktcm, vec, 'm', BPMF::M); + ASSIGNKEY1(ktcm, vec, 'f', BPMF::F); + ASSIGNKEY1(ktcm, vec, 'd', BPMF::D); + ASSIGNKEY1(ktcm, vec, 't', BPMF::T); + ASSIGNKEY1(ktcm, vec, 'n', BPMF::N); + ASSIGNKEY1(ktcm, vec, 'l', BPMF::L); + ASSIGNKEY1(ktcm, vec, 'v', BPMF::G); + ASSIGNKEY1(ktcm, vec, 'k', BPMF::K); + ASSIGNKEY1(ktcm, vec, 'h', BPMF::H); + ASSIGNKEY1(ktcm, vec, 'g', BPMF::J); + ASSIGNKEY1(ktcm, vec, '7', BPMF::Q); + ASSIGNKEY1(ktcm, vec, 'c', BPMF::X); + ASSIGNKEY1(ktcm, vec, ',', BPMF::ZH); + ASSIGNKEY1(ktcm, vec, '.', BPMF::CH); + ASSIGNKEY1(ktcm, vec, '/', BPMF::SH); + ASSIGNKEY1(ktcm, vec, 'j', BPMF::R); + ASSIGNKEY1(ktcm, vec, ';', BPMF::Z); + ASSIGNKEY1(ktcm, vec, '\'', BPMF::C); + ASSIGNKEY1(ktcm, vec, 's', BPMF::S); + ASSIGNKEY1(ktcm, vec, 'e', BPMF::I); + ASSIGNKEY1(ktcm, vec, 'x', BPMF::U); + ASSIGNKEY1(ktcm, vec, 'u', BPMF::UE); + ASSIGNKEY1(ktcm, vec, 'a', BPMF::A); + ASSIGNKEY1(ktcm, vec, 'o', BPMF::O); + ASSIGNKEY1(ktcm, vec, 'r', BPMF::ER); + ASSIGNKEY1(ktcm, vec, 'w', BPMF::E); + ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI); + ASSIGNKEY1(ktcm, vec, 'q', BPMF::EI); + ASSIGNKEY1(ktcm, vec, 'z', BPMF::AO); + ASSIGNKEY1(ktcm, vec, 'y', BPMF::OU); + ASSIGNKEY1(ktcm, vec, '8', BPMF::AN); + ASSIGNKEY1(ktcm, vec, '9', BPMF::EN); + ASSIGNKEY1(ktcm, vec, '0', BPMF::ANG); + ASSIGNKEY1(ktcm, vec, '-', BPMF::ENG); + ASSIGNKEY1(ktcm, vec, '=', BPMF::ERR); + ASSIGNKEY1(ktcm, vec, '2', BPMF::Tone2); + ASSIGNKEY1(ktcm, vec, '3', BPMF::Tone3); + ASSIGNKEY1(ktcm, vec, '4', BPMF::Tone4); + ASSIGNKEY1(ktcm, vec, '1', BPMF::Tone5); - return c_IBMLayout; + return new BopomofoKeyboardLayout(ktcm, "ETen"); } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETenLayout() -{ - if (!c_ETenLayout) { - vector vec; - BopomofoKeyToComponentMap ktcm; - - ASSIGNKEY1(ktcm, vec, 'b', BPMF::B); - ASSIGNKEY1(ktcm, vec, 'p', BPMF::P); - ASSIGNKEY1(ktcm, vec, 'm', BPMF::M); - ASSIGNKEY1(ktcm, vec, 'f', BPMF::F); - ASSIGNKEY1(ktcm, vec, 'd', BPMF::D); - ASSIGNKEY1(ktcm, vec, 't', BPMF::T); - ASSIGNKEY1(ktcm, vec, 'n', BPMF::N); - ASSIGNKEY1(ktcm, vec, 'l', BPMF::L); - ASSIGNKEY1(ktcm, vec, 'v', BPMF::G); - ASSIGNKEY1(ktcm, vec, 'k', BPMF::K); - ASSIGNKEY1(ktcm, vec, 'h', BPMF::H); - ASSIGNKEY1(ktcm, vec, 'g', BPMF::J); - ASSIGNKEY1(ktcm, vec, '7', BPMF::Q); - ASSIGNKEY1(ktcm, vec, 'c', BPMF::X); - ASSIGNKEY1(ktcm, vec, ',', BPMF::ZH); - ASSIGNKEY1(ktcm, vec, '.', BPMF::CH); - ASSIGNKEY1(ktcm, vec, '/', BPMF::SH); - ASSIGNKEY1(ktcm, vec, 'j', BPMF::R); - ASSIGNKEY1(ktcm, vec, ';', BPMF::Z); - ASSIGNKEY1(ktcm, vec, '\'', BPMF::C); - ASSIGNKEY1(ktcm, vec, 's', BPMF::S); - ASSIGNKEY1(ktcm, vec, 'e', BPMF::I); - ASSIGNKEY1(ktcm, vec, 'x', BPMF::U); - ASSIGNKEY1(ktcm, vec, 'u', BPMF::UE); - ASSIGNKEY1(ktcm, vec, 'a', BPMF::A); - ASSIGNKEY1(ktcm, vec, 'o', BPMF::O); - ASSIGNKEY1(ktcm, vec, 'r', BPMF::ER); - ASSIGNKEY1(ktcm, vec, 'w', BPMF::E); - ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI); - ASSIGNKEY1(ktcm, vec, 'q', BPMF::EI); - ASSIGNKEY1(ktcm, vec, 'z', BPMF::AO); - ASSIGNKEY1(ktcm, vec, 'y', BPMF::OU); - ASSIGNKEY1(ktcm, vec, '8', BPMF::AN); - ASSIGNKEY1(ktcm, vec, '9', BPMF::EN); - ASSIGNKEY1(ktcm, vec, '0', BPMF::ANG); - ASSIGNKEY1(ktcm, vec, '-', BPMF::ENG); - ASSIGNKEY1(ktcm, vec, '=', BPMF::ERR); - ASSIGNKEY1(ktcm, vec, '2', BPMF::Tone2); - ASSIGNKEY1(ktcm, vec, '3', BPMF::Tone3); - ASSIGNKEY1(ktcm, vec, '4', BPMF::Tone4); - ASSIGNKEY1(ktcm, vec, '1', BPMF::Tone5); - - c_ETenLayout = new BopomofoKeyboardLayout(ktcm, "ETen"); - } +static BopomofoKeyboardLayout* CreateHsuLayout() { + std::vector vec; + BopomofoKeyToComponentMap ktcm; - return c_ETenLayout; + ASSIGNKEY1(ktcm, vec, 'b', BPMF::B); + ASSIGNKEY1(ktcm, vec, 'p', BPMF::P); + ASSIGNKEY2(ktcm, vec, 'm', BPMF::M, BPMF::AN); + ASSIGNKEY2(ktcm, vec, 'f', BPMF::F, BPMF::Tone3); + ASSIGNKEY2(ktcm, vec, 'd', BPMF::D, BPMF::Tone2); + ASSIGNKEY1(ktcm, vec, 't', BPMF::T); + ASSIGNKEY2(ktcm, vec, 'n', BPMF::N, BPMF::EN); + ASSIGNKEY3(ktcm, vec, 'l', BPMF::L, BPMF::ENG, BPMF::ERR); + ASSIGNKEY2(ktcm, vec, 'g', BPMF::G, BPMF::ER); + ASSIGNKEY2(ktcm, vec, 'k', BPMF::K, BPMF::ANG); + ASSIGNKEY2(ktcm, vec, 'h', BPMF::H, BPMF::O); + ASSIGNKEY3(ktcm, vec, 'j', BPMF::J, BPMF::ZH, BPMF::Tone4); + ASSIGNKEY2(ktcm, vec, 'v', BPMF::Q, BPMF::CH); + ASSIGNKEY2(ktcm, vec, 'c', BPMF::X, BPMF::SH); + ASSIGNKEY1(ktcm, vec, 'r', BPMF::R); + ASSIGNKEY1(ktcm, vec, 'z', BPMF::Z); + ASSIGNKEY2(ktcm, vec, 'a', BPMF::C, BPMF::EI); + ASSIGNKEY2(ktcm, vec, 's', BPMF::S, BPMF::Tone5); + ASSIGNKEY2(ktcm, vec, 'e', BPMF::I, BPMF::E); + ASSIGNKEY1(ktcm, vec, 'x', BPMF::U); + ASSIGNKEY1(ktcm, vec, 'u', BPMF::UE); + ASSIGNKEY1(ktcm, vec, 'y', BPMF::A); + ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI); + ASSIGNKEY1(ktcm, vec, 'w', BPMF::AO); + ASSIGNKEY1(ktcm, vec, 'o', BPMF::OU); + + return new BopomofoKeyboardLayout(ktcm, "Hsu"); } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HsuLayout() -{ - if (!c_HsuLayout) { - vector vec; - BopomofoKeyToComponentMap ktcm; - - ASSIGNKEY1(ktcm, vec, 'b', BPMF::B); - ASSIGNKEY1(ktcm, vec, 'p', BPMF::P); - ASSIGNKEY2(ktcm, vec, 'm', BPMF::M, BPMF::AN); - ASSIGNKEY2(ktcm, vec, 'f', BPMF::F, BPMF::Tone3); - ASSIGNKEY2(ktcm, vec, 'd', BPMF::D, BPMF::Tone2); - ASSIGNKEY1(ktcm, vec, 't', BPMF::T); - ASSIGNKEY2(ktcm, vec, 'n', BPMF::N, BPMF::EN); - ASSIGNKEY3(ktcm, vec, 'l', BPMF::L, BPMF::ENG, BPMF::ERR); - ASSIGNKEY2(ktcm, vec, 'g', BPMF::G, BPMF::ER); - ASSIGNKEY2(ktcm, vec, 'k', BPMF::K, BPMF::ANG); - ASSIGNKEY2(ktcm, vec, 'h', BPMF::H, BPMF::O); - ASSIGNKEY3(ktcm, vec, 'j', BPMF::J, BPMF::ZH, BPMF::Tone4); - ASSIGNKEY2(ktcm, vec, 'v', BPMF::Q, BPMF::CH); - ASSIGNKEY2(ktcm, vec, 'c', BPMF::X, BPMF::SH); - ASSIGNKEY1(ktcm, vec, 'r', BPMF::R); - ASSIGNKEY1(ktcm, vec, 'z', BPMF::Z); - ASSIGNKEY2(ktcm, vec, 'a', BPMF::C, BPMF::EI); - ASSIGNKEY2(ktcm, vec, 's', BPMF::S, BPMF::Tone5); - ASSIGNKEY2(ktcm, vec, 'e', BPMF::I, BPMF::E); - ASSIGNKEY1(ktcm, vec, 'x', BPMF::U); - ASSIGNKEY1(ktcm, vec, 'u', BPMF::UE); - ASSIGNKEY1(ktcm, vec, 'y', BPMF::A); - ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI); - ASSIGNKEY1(ktcm, vec, 'w', BPMF::AO); - ASSIGNKEY1(ktcm, vec, 'o', BPMF::OU); - - c_HsuLayout = new BopomofoKeyboardLayout(ktcm, "Hsu"); - } +static BopomofoKeyboardLayout* CreateETen26Layout() { + std::vector vec; + BopomofoKeyToComponentMap ktcm; - return c_HsuLayout; + ASSIGNKEY1(ktcm, vec, 'b', BPMF::B); + ASSIGNKEY2(ktcm, vec, 'p', BPMF::P, BPMF::OU); + ASSIGNKEY2(ktcm, vec, 'm', BPMF::M, BPMF::AN); + ASSIGNKEY2(ktcm, vec, 'f', BPMF::F, BPMF::Tone2); + ASSIGNKEY2(ktcm, vec, 'd', BPMF::D, BPMF::Tone5); + ASSIGNKEY2(ktcm, vec, 't', BPMF::T, BPMF::ANG); + ASSIGNKEY2(ktcm, vec, 'n', BPMF::N, BPMF::EN); + ASSIGNKEY2(ktcm, vec, 'l', BPMF::L, BPMF::ENG); + ASSIGNKEY2(ktcm, vec, 'v', BPMF::G, BPMF::Q); + ASSIGNKEY2(ktcm, vec, 'k', BPMF::K, BPMF::Tone4); + ASSIGNKEY2(ktcm, vec, 'h', BPMF::H, BPMF::ERR); + ASSIGNKEY2(ktcm, vec, 'g', BPMF::ZH, BPMF::J); + ASSIGNKEY2(ktcm, vec, 'c', BPMF::SH, BPMF::X); + ASSIGNKEY1(ktcm, vec, 'y', BPMF::CH); + ASSIGNKEY2(ktcm, vec, 'j', BPMF::R, BPMF::Tone3); + ASSIGNKEY2(ktcm, vec, 'q', BPMF::Z, BPMF::EI); + ASSIGNKEY2(ktcm, vec, 'w', BPMF::C, BPMF::E); + ASSIGNKEY1(ktcm, vec, 's', BPMF::S); + ASSIGNKEY1(ktcm, vec, 'e', BPMF::I); + ASSIGNKEY1(ktcm, vec, 'x', BPMF::U); + ASSIGNKEY1(ktcm, vec, 'u', BPMF::UE); + ASSIGNKEY1(ktcm, vec, 'a', BPMF::A); + ASSIGNKEY1(ktcm, vec, 'o', BPMF::O); + ASSIGNKEY1(ktcm, vec, 'r', BPMF::ER); + ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI); + ASSIGNKEY1(ktcm, vec, 'z', BPMF::AO); + return new BopomofoKeyboardLayout(ktcm, "ETen26"); } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETen26Layout() -{ - if (!c_ETen26Layout) { - vector vec; - BopomofoKeyToComponentMap ktcm; - - ASSIGNKEY1(ktcm, vec, 'b', BPMF::B); - ASSIGNKEY2(ktcm, vec, 'p', BPMF::P, BPMF::OU); - ASSIGNKEY2(ktcm, vec, 'm', BPMF::M, BPMF::AN); - ASSIGNKEY2(ktcm, vec, 'f', BPMF::F, BPMF::Tone2); - ASSIGNKEY2(ktcm, vec, 'd', BPMF::D, BPMF::Tone5); - ASSIGNKEY2(ktcm, vec, 't', BPMF::T, BPMF::ANG); - ASSIGNKEY2(ktcm, vec, 'n', BPMF::N, BPMF::EN); - ASSIGNKEY2(ktcm, vec, 'l', BPMF::L, BPMF::ENG); - ASSIGNKEY2(ktcm, vec, 'v', BPMF::G, BPMF::Q); - ASSIGNKEY2(ktcm, vec, 'k', BPMF::K, BPMF::Tone4); - ASSIGNKEY2(ktcm, vec, 'h', BPMF::H, BPMF::ERR); - ASSIGNKEY2(ktcm, vec, 'g', BPMF::ZH, BPMF::J); - ASSIGNKEY2(ktcm, vec, 'c', BPMF::SH, BPMF::X); - ASSIGNKEY1(ktcm, vec, 'y', BPMF::CH); - ASSIGNKEY2(ktcm, vec, 'j', BPMF::R, BPMF::Tone3); - ASSIGNKEY2(ktcm, vec, 'q', BPMF::Z, BPMF::EI); - ASSIGNKEY2(ktcm, vec, 'w', BPMF::C, BPMF::E); - ASSIGNKEY1(ktcm, vec, 's', BPMF::S); - ASSIGNKEY1(ktcm, vec, 'e', BPMF::I); - ASSIGNKEY1(ktcm, vec, 'x', BPMF::U); - ASSIGNKEY1(ktcm, vec, 'u', BPMF::UE); - ASSIGNKEY1(ktcm, vec, 'a', BPMF::A); - ASSIGNKEY1(ktcm, vec, 'o', BPMF::O); - ASSIGNKEY1(ktcm, vec, 'r', BPMF::ER); - ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI); - ASSIGNKEY1(ktcm, vec, 'z', BPMF::AO); - - c_ETen26Layout = new BopomofoKeyboardLayout(ktcm, "ETen26"); - } - - return c_ETen26Layout; + +static BopomofoKeyboardLayout* CreateHanyuPinyinLayout() { + BopomofoKeyToComponentMap ktcm; + return new BopomofoKeyboardLayout(ktcm, "HanyuPinyin"); } -const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HanyuPinyinLayout() -{ - if (!c_HanyuPinyinLayout) { - BopomofoKeyToComponentMap ktcm; - c_HanyuPinyinLayout = new BopomofoKeyboardLayout(ktcm, "HanyuPinyin"); - } - return c_HanyuPinyinLayout; + +const BopomofoKeyboardLayout* BopomofoKeyboardLayout::StandardLayout() { + static BopomofoKeyboardLayout* layout = CreateStandardLayout(); + return layout; +} + +const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETenLayout() { + static BopomofoKeyboardLayout* layout = CreateETenLayout(); + return layout; } +const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HsuLayout() { + static BopomofoKeyboardLayout* layout = CreateHsuLayout(); + return layout; +} + +const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETen26Layout() { + static BopomofoKeyboardLayout* layout = CreateETen26Layout(); + return layout; +} + +const BopomofoKeyboardLayout* BopomofoKeyboardLayout::IBMLayout() { + static BopomofoKeyboardLayout* layout = CreateIBMLayout(); + return layout; +} + +const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HanyuPinyinLayout() { + static BopomofoKeyboardLayout* layout = CreateHanyuPinyinLayout(); + return layout; +} -} // namespace Mandarin -} // namespace Taiyan +} // namespace Mandarin +} // namespace Taiyan -- Gitee From 4ec1d3b1a844a64d946d910303243b8fb3fb999f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 2 Feb 2022 13:59:14 +0800 Subject: [PATCH 153/163] Pref // Remove Unnecessary Kanji Conv Engine Pref. --- Source/PreferencesModule.swift | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/Source/PreferencesModule.swift b/Source/PreferencesModule.swift index 48d7d3a78..5717fe3bf 100644 --- a/Source/PreferencesModule.swift +++ b/Source/PreferencesModule.swift @@ -33,7 +33,6 @@ private let kShouldNotFartInLieuOfBeep = "ShouldNotFartInLieuOfBeep" private let kCandidateTextFontName = "CandidateTextFontName" private let kCandidateKeyLabelFontName = "CandidateKeyLabelFontName" private let kCandidateKeys = "CandidateKeys" -private let kChineseConversionEngine = "ChineseConversionEngine" private let kPhraseReplacementEnabled = "PhraseReplacementEnabled" private let kDefaultCandidateListTextSize: CGFloat = 18 @@ -154,20 +153,6 @@ struct ComposingBufferSize { } } -@objc enum ChineseConversionEngine: Int { - case openCC - case vxHanConvert - - var name: String { - switch (self) { - case .openCC: - return "OpenCC" - case .vxHanConvert: - return "VXHanConvert" - } - } -} - // MARK: - @objc public class Preferences: NSObject { static func reset() { @@ -193,7 +178,6 @@ struct ComposingBufferSize { defaults.removeObject(forKey: kCandidateKeyLabelFontName) defaults.removeObject(forKey: kCandidateKeys) defaults.removeObject(forKey: kPhraseReplacementEnabled) - defaults.removeObject(forKey: kChineseConversionEngine) defaults.removeObject(forKey: kUseWinNT351BPMF) defaults.removeObject(forKey: kMaxCandidateLength) defaults.removeObject(forKey: kShouldNotFartInLieuOfBeep) @@ -430,13 +414,6 @@ struct ComposingBufferSize { } - @UserDefault(key: kChineseConversionEngine, defaultValue: 0) - @objc static var chineseConversionEngine: Int - - @objc static var chineseConversionEngineName: String? { - return ChineseConversionEngine(rawValue: chineseConversionEngine)?.name - } - @UserDefault(key: kPhraseReplacementEnabled, defaultValue: false) @objc static var phraseReplacementEnabled: Bool -- Gitee From 67af8b40b187b8ab014ec25067028f241b8c4135 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 2 Feb 2022 14:58:22 +0800 Subject: [PATCH 154/163] Pref // Remove national flags from keyboard layout menu. - for faster GUI rendering speed. --- Source/PreferencesWindowController.swift | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index b0e829003..bd39008ab 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -112,20 +112,6 @@ extension RangeReplaceableCollection where Element: Hashable { menuItem.title = localizedName menuItem.representedObject = sourceID - if let iconPtr = TISGetInputSourceProperty(source, kTISPropertyIconRef) { - let icon = IconRef(iconPtr) - let image = NSImage(iconRef: icon) - - func resize( _ image: NSImage) -> NSImage { - let newImage = NSImage(size: NSSize(width: 16, height: 16)) - newImage.lockFocus() - image.draw(in: NSRect(x: 0, y: 0, width: 16, height: 16)) - newImage.unlockFocus() - return newImage - } - menuItem.image = resize(image) - } - if sourceID == "com.apple.keylayout.US" { usKeyboardLayoutItem = menuItem } -- Gitee From 02b17330317094a1f32a49bfbe0a605ac22ae98c Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 2 Feb 2022 14:58:32 +0800 Subject: [PATCH 155/163] Pref // Add Apple ZhuyinBopomofo to basisKeyboardLayout menu. --- Source/PreferencesWindowController.swift | 6 ++++++ Source/en.lproj/Localizable.strings | 1 + Source/ja.lproj/Localizable.strings | 1 + Source/zh-Hans.lproj/Localizable.strings | 1 + Source/zh-Hant.lproj/Localizable.strings | 2 ++ 5 files changed, 11 insertions(+) diff --git a/Source/PreferencesWindowController.swift b/Source/PreferencesWindowController.swift index bd39008ab..4729477af 100644 --- a/Source/PreferencesWindowController.swift +++ b/Source/PreferencesWindowController.swift @@ -63,6 +63,7 @@ extension RangeReplaceableCollection where Element: Hashable { basisKeyboardLayoutButton.menu?.removeAllItems() let basisKeyboardLayoutID = Preferences.basisKeyboardLayout + for source in list { func getString(_ key: CFString) -> String? { @@ -121,6 +122,11 @@ extension RangeReplaceableCollection where Element: Hashable { basisKeyboardLayoutButton.menu?.addItem(menuItem) } + let menuItem = NSMenuItem() + menuItem.title = String(format: NSLocalizedString("Apple Zhuyin Bopomofo", comment: "")) + menuItem.representedObject = String("com.apple.keylayout.ZhuyinBopomofo") + basisKeyboardLayoutButton.menu?.addItem(menuItem) + basisKeyboardLayoutButton.select(chosenItem ?? usKeyboardLayoutItem) selectionKeyComboBox.usesDataSource = false selectionKeyComboBox.removeAllItems() diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 8b897e3bc..0a3d67201 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -43,3 +43,4 @@ "zh-Hans" = "Simplified Chinese"; "zh-Hant" = "Traditional Chinese"; "ja" = "Japanese"; +"Apple Zhuyin Bopomofo" = "Apple Zhuyin Bopomofo"; diff --git a/Source/ja.lproj/Localizable.strings b/Source/ja.lproj/Localizable.strings index 0224e8028..40e62b1c1 100644 --- a/Source/ja.lproj/Localizable.strings +++ b/Source/ja.lproj/Localizable.strings @@ -43,3 +43,4 @@ "zh-Hans" = "簡體中国語"; "zh-Hant" = "繁體中国語"; "ja" = "和語"; +"Apple Zhuyin Bopomofo" = "Apple 注音ボポモフォ配列"; diff --git a/Source/zh-Hans.lproj/Localizable.strings b/Source/zh-Hans.lproj/Localizable.strings index 5d6fb0693..7f543a9d7 100644 --- a/Source/zh-Hans.lproj/Localizable.strings +++ b/Source/zh-Hans.lproj/Localizable.strings @@ -43,3 +43,4 @@ "zh-Hans" = "简体中文"; "zh-Hant" = "繁体中文"; "ja" = "和文"; +"Apple Zhuyin Bopomofo" = "Apple 注音键盘布局"; diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index b0bc929f1..55a0f3436 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -43,3 +43,5 @@ "zh-Hans" = "簡體中文"; "zh-Hant" = "繁體中文"; "ja" = "和文"; +"Apple Zhuyin Bopomofo" = "Apple Zhuyin Bopomofo"; +"Apple Zhuyin Bopomofo" = "Apple 注音鍵盤佈局"; -- Gitee From 5cb4dd25f0abafff3ac872612119b1de92d943aa Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Wed, 2 Feb 2022 17:44:56 +0800 Subject: [PATCH 156/163] Pref // Tweaking layouts and terms. - Avoid using trademarks of 3rd-party commercial products, even if the original products are abandonwares. - Simplified the tab contents of the Preferences window. Though its design was inspired by Microsoft New Phonetic 2007, the design doesn't have to be too similar to it. --- Source/Base.lproj/preferences.xib | 120 ++++++++---------- Source/Engine/ControllerModules/KeyHandler.mm | 17 +-- Source/InputMethodController.mm | 12 +- Source/PreferencesModule.swift | 22 ++-- Source/en.lproj/Localizable.strings | 2 +- Source/en.lproj/preferences.strings | 15 +-- Source/ja.lproj/Localizable.strings | 2 +- Source/ja.lproj/preferences.strings | 11 +- Source/zh-Hans.lproj/Localizable.strings | 2 +- Source/zh-Hans.lproj/preferences.strings | 15 +-- Source/zh-Hant.lproj/Localizable.strings | 2 +- Source/zh-Hant.lproj/preferences.strings | 13 +- 12 files changed, 107 insertions(+), 126 deletions(-) diff --git a/Source/Base.lproj/preferences.xib b/Source/Base.lproj/preferences.xib index 5693b12a0..29555c8e5 100644 --- a/Source/Base.lproj/preferences.xib +++ b/Source/Base.lproj/preferences.xib @@ -19,25 +19,25 @@ - - + + - + - + - + - + - + @@ -204,7 +204,7 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Item 1 - Item 2 - Item 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/zh-Hant.lproj/preferences.strings b/Source/zh-Hant.lproj/preferences.strings new file mode 100644 index 000000000..05de70d85 --- /dev/null +++ b/Source/zh-Hant.lproj/preferences.strings @@ -0,0 +1,108 @@ + +/* Class = "NSWindow"; title = "Bopomofo Preferences"; ObjectID = "1"; */ +"1.title" = "注音偏好設定"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "5"; */ +"5.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "Standard"; ObjectID = "6"; */ +"6.title" = "大千"; + +/* Class = "NSMenuItem"; title = "ETen"; ObjectID = "7"; */ +"7.title" = "倚天傳統"; + +/* Class = "NSMenuItem"; title = "Hsu"; ObjectID = "8"; */ +"8.title" = "許氏"; + +/* Class = "NSMenuItem"; title = "ETen26"; ObjectID = "9"; */ +"9.title" = "倚天二六"; + +/* Class = "NSMenuItem"; title = "Hanyu Pinyin"; ObjectID = "10"; */ +"10.title" = "漢語拼音+數字標調"; + +/* Class = "NSTextFieldCell"; title = "Bopomofo Keyboard Layout:"; ObjectID = "12"; */ +"12.title" = "注音鍵盤佈局:"; + +/* Class = "NSTextFieldCell"; title = "Show Candidate Phrase:"; ObjectID = "14"; */ +"14.title" = "用於顯示候選詞的游標定位:"; + +/* Class = "NSButtonCell"; title = "Before the cursor (like Hanin)"; ObjectID = "16"; */ +"16.title" = "游標前置(像松下漢音輸入法)"; + +/* Class = "NSButtonCell"; title = "After the cursor (like MS IME)"; ObjectID = "17"; */ +"17.title" = "游標後置(像微軟新注音)"; + +/* Class = "NSButtonCell"; title = "Radio"; ObjectID = "18"; */ +"18.title" = "Radio"; + +/* Class = "NSButtonCell"; title = "Radio"; ObjectID = "20"; */ +"20.title" = "Radio"; + +/* Class = "NSButtonCell"; title = "Horizontal"; ObjectID = "21"; */ +"21.title" = "橫向"; + +/* Class = "NSButtonCell"; title = "Vertical"; ObjectID = "22"; */ +"22.title" = "縱向"; + +/* Class = "NSTextFieldCell"; title = "Candidate List Style:"; ObjectID = "24"; */ +"24.title" = "候選詞窗格排版:"; + +/* Class = "NSTextFieldCell"; title = "Candidate Text Size:"; ObjectID = "29"; */ +"29.title" = "候選詞窗格字型大小:"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "92"; */ +"92.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "12"; ObjectID = "93"; */ +"93.title" = "12"; + +/* Class = "NSMenuItem"; title = "14"; ObjectID = "94"; */ +"94.title" = "14"; + +/* Class = "NSMenuItem"; title = "16"; ObjectID = "95"; */ +"95.title" = "16"; + +/* Class = "NSMenuItem"; title = "18"; ObjectID = "96"; */ +"96.title" = "18"; + +/* Class = "NSMenuItem"; title = "24"; ObjectID = "98"; */ +"98.title" = "24"; + +/* Class = "NSMenuItem"; title = "32"; ObjectID = "99"; */ +"99.title" = "32"; + +/* Class = "NSMenuItem"; title = "64"; ObjectID = "100"; */ +"100.title" = "64"; + +/* Class = "NSMenuItem"; title = "96"; ObjectID = "101"; */ +"101.title" = "96"; + +/* Class = "NSButtonCell"; title = "Space key chooses candidate"; ObjectID = "110"; */ +"110.title" = "摁空格鍵以選取候選詞"; + +/* Class = "NSTextFieldCell"; title = "Alphanumeric Keyboard Layout:"; ObjectID = "126"; */ +"126.title" = "英數鍵盤佈局:"; + +/* Class = "NSMenu"; title = "OtherViews"; ObjectID = "128"; */ +"128.title" = "OtherViews"; + +/* Class = "NSMenuItem"; title = "IBM"; ObjectID = "137"; */ +"137.title" = "IBM"; + +/* Class = "NSTextFieldCell"; title = "Selection Keys:"; ObjectID = "FnD-oH-El5"; */ +"FnD-oH-El5.title" = "選字鍵:"; + +/* Class = "NSButtonCell"; title = "Check for updates automatically"; ObjectID = "Z9t-P0-BLF"; */ +"Z9t-P0-BLF.title" = "自動檢查軟體更新"; + +/* Class = "NSButtonCell"; title = "ESC key clears entire input buffer"; ObjectID = "f2j-xD-4xK"; */ +"f2j-xD-4xK.title" = "敲 ESC 鍵以清空整個輸入緩衝區"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[0] = "Item 1"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[0]" = "Item 1"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[1] = "Item 2"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[1]" = "Item 2"; + +/* Class = "NSComboBoxCell"; jQC-12-UuK.ibShadowedObjectValues[2] = "Item 3"; ObjectID = "jQC-12-UuK"; */ +"jQC-12-UuK.ibShadowedObjectValues[2]" = "Item 3"; diff --git a/Source/zh-Hant.lproj/preferences.xib b/Source/zh-Hant.lproj/preferences.xib deleted file mode 100644 index 37397ed02..000000000 --- a/Source/zh-Hant.lproj/preferences.xib +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Item 1 - Item 2 - Item 3 - - - - - - - - - - - - - - - - - - - - - - diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 3b62dbf6c..c9317aeb0 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -71,6 +71,14 @@ /* Begin PBXFileReference section */ 5B000FC1278495AD004F02AC /* SimpBopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = SimpBopomofo.tiff; sourceTree = ""; }; 5B000FC2278495AD004F02AC /* SimpBopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "SimpBopomofo@2x.tiff"; sourceTree = ""; }; + 5B054058278787710083EF4A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/preferences.strings; sourceTree = ""; }; + 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserOverrideModel.cpp; sourceTree = ""; }; + 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserOverrideModel.h; sourceTree = ""; }; + 5B42B64127877D6500BB9B9F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/preferences.strings"; sourceTree = ""; }; + 5B42B64227877D7700BB9B9F /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "Source/zh-Hant.lproj/preferences.strings"; sourceTree = ""; }; + 5B58E87E278413E7003EA2AD /* en */ = {isa = PBXFileReference; lastKnownFileType = text; name = en; path = Source/en.lproj/MITLicense.txt; sourceTree = SOURCE_ROOT; }; + 5B58E880278413EF003EA2AD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hans"; path = "zh-Hans.lproj/MITLicense.txt"; sourceTree = ""; }; + 5B58E881278413F1003EA2AD /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text; name = "zh-Hant"; path = "zh-Hant.lproj/MITLicense.txt"; sourceTree = ""; }; 5B9781D32763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D42763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = "zh-Hans"; path = "zh-Hans.lproj/License.rtf"; sourceTree = ""; }; 5B9781D52763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -78,7 +86,6 @@ 5B9781D72763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D92763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.xib"; sourceTree = ""; }; - 5B9781DA2763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "Source/zh-Hans.lproj/preferences.xib"; sourceTree = ""; }; 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; 6A0421A615FEF3F50061ED63 /* FastLM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FastLM.cpp; sourceTree = ""; }; 6A0421A715FEF3F50061ED63 /* FastLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FastLM.h; sourceTree = ""; }; @@ -160,7 +167,6 @@ 6A0D4F4B15FC0EE100ABF4B3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Source/en.lproj/Localizable.strings; sourceTree = ""; }; 6A0D4F5415FC0EF900ABF4B3 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "Source/zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; 6A0D4F5515FC0EF900ABF4B3 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "Source/zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; - 6A0D4F5615FC0EF900ABF4B3 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hant"; path = "Source/zh-Hant.lproj/preferences.xib"; sourceTree = ""; }; 6A15B32421A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 6A15B32721A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Source/Base.lproj/preferences.xib; sourceTree = ""; }; @@ -692,9 +698,10 @@ 6A0D4F4E15FC0EE100ABF4B3 /* preferences.xib */ = { isa = PBXVariantGroup; children = ( - 6A0D4F5615FC0EF900ABF4B3 /* zh-Hant */, 6A15B32721A51F2300B92CD3 /* Base */, - 5B9781DA2763850700897999 /* zh-Hans */, + 5B42B64127877D6500BB9B9F /* zh-Hans */, + 5B42B64227877D7700BB9B9F /* zh-Hant */, + 5B054058278787710083EF4A /* en */, ); name = preferences.xib; path = ..; -- Gitee From 89e663ebf6253cd6a318db08a916d708279682c4 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 7 Jan 2022 02:56:38 +0800 Subject: [PATCH 013/163] Lukhnos: Tweaks to the exponential decay-based user candidate override model. --- Source/Engine/Gramambular/Grid.h | 35 ++++- Source/InputMethodController.mm | 225 ++++--------------------------- 2 files changed, 61 insertions(+), 199 deletions(-) diff --git a/Source/Engine/Gramambular/Grid.h b/Source/Engine/Gramambular/Grid.h index e13c8eab8..770263793 100644 --- a/Source/Engine/Gramambular/Grid.h +++ b/Source/Engine/Gramambular/Grid.h @@ -47,8 +47,19 @@ namespace Formosa { size_t width() const; vector nodesEndingAt(size_t inLocation); vector nodesCrossingOrEndingAt(size_t inLocation); - void fixNodeSelectedCandidate(size_t location, const string& value); - + + // "Freeze" the node with the unigram that represents the selected canditate value. + // After this, the node that contains the unigram will always be evaluated to that + // unigram, while all other overlapping nodes will be reset to their initial state + // (that is, if any of those nodes were "frozen" or fixed, they will be unfrozen.) + void fixNodeSelectedCandidate(size_t location, const string& value); + + // Similar to fixNodeSelectedCandidate, but instead of "freezing" the node, only + // boost the unigram that represents the value with an overriding score. This + // has the same side effect as fixNodeSelectedCandidate, which is that all other + // overlapping nodes will be reset to their initial state. + void overrideNodeScoreForSelectedCandidate(size_t location, const string& value, float overridingScore); + const string dumpDOT(); protected: @@ -195,7 +206,25 @@ namespace Formosa { } } - inline const string Grid::dumpDOT() + inline void Grid::overrideNodeScoreForSelectedCandidate(size_t location, const string& value, float overridingScore) + { + vector nodes = nodesCrossingOrEndingAt(location); + for (auto nodeAnchor : nodes) { + auto candidates = nodeAnchor.node->candidates(); + + // Reset the candidate-fixed state of every node at the location. + const_cast(nodeAnchor.node)->resetCandidate(); + + for (size_t i = 0, c = candidates.size(); i < c; ++i) { + if (candidates[i].value == value) { + const_cast(nodeAnchor.node)->selectFloatingCandidateAtIndex(i, overridingScore); + break; + } + } + } + } + + inline const string Grid::dumpDOT() { stringstream sst; sst << "digraph {" << endl; diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 17b7bb072..64e009bce 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -76,7 +76,6 @@ static NSString *const kCandidateListTextSizeKey = @"CandidateListTextSize"; static NSString *const kSelectPhraseAfterCursorAsCandidatePreferenceKey = @"SelectPhraseAfterCursorAsCandidate"; static NSString *const kUseHorizontalCandidateListPreferenceKey = @"UseHorizontalCandidateList"; static NSString *const kComposingBufferSizePreferenceKey = @"ComposingBufferSize"; -static NSString *const kDisableUserCandidateSelectionLearning = @"DisableUserCandidateSelectionLearning"; static NSString *const kChooseCandidateUsingSpaceKey = @"ChooseCandidateUsingSpaceKey"; static NSString *const kChineseConversionEnabledKey = @"ChineseConversionEnabledKey"; static NSString *const kEscToCleanInputBufferKey = @"EscToCleanInputBufferKey"; @@ -104,9 +103,6 @@ enum { kDeleteKeyCode = 117 }; -// a global object for saving the "learned" user candidate selections -NSMutableDictionary *gCandidateLearningDictionary = nil; -NSString *gUserCandidatesDictionaryPath = nil; VTCandidateController *gCurrentCandidateController = nil; // if DEBUG is defined, a DOT file (GraphViz format) will be written to the @@ -119,6 +115,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/vChewing-visualization.dot"; FastLM gLanguageModel; FastLM gLanguageModelSimpBopomofo; +static const int kUserOverrideModelCapacity = 500; +static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr. +vChewing::UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife); + // https://clang-analyzer.llvm.org/faq.html __attribute__((annotate("returns_localized_nsstring"))) static inline NSString *LocalizationNotNeeded(NSString *s) { @@ -133,10 +133,7 @@ static inline NSString *LocalizationNotNeeded(NSString *s) { - (void)collectCandidates; - (size_t)actualCandidateCursorIndex; -- (NSString *)neighborTrigramString; -- (void)_performDeferredSaveUserCandidatesDictionary; -- (void)saveUserCandidatesDictionary; - (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client; - (void)beep; @@ -153,6 +150,19 @@ public: } }; +static const double kEpsilon = 0.000001; + +static double FindHighestScore(const vector& nodes, double epsilon) { + double highestScore = 0.0; + for (auto ni = nodes.begin(), ne = nodes.end(); ni != ne; ++ni) { + double score = ni->node->highestUnigramScore(); + if (score > highestScore) { + highestScore = score; + } + } + return highestScore + epsilon; +} + @implementation vChewingInputMethodController - (void)dealloc { @@ -185,6 +195,7 @@ public: // create the lattice builder _languageModel = &gLanguageModel; _builder = new BlockReadingBuilder(_languageModel); + _uom = &gUserOverrideModel; // each Mandarin syllable is separated by a hyphen _builder->setJoinSeparator("-"); @@ -192,11 +203,6 @@ public: // create the composing buffer _composingBuffer = [[NSMutableString alloc] init]; - // populate the settings, by default, DISABLE user candidate learning - if (![[NSUserDefaults standardUserDefaults] objectForKey:kDisableUserCandidateSelectionLearning]) { - [[NSUserDefaults standardUserDefaults] setObject:(id)kCFBooleanTrue forKey:kDisableUserCandidateSelectionLearning]; - } - _inputMode = kBopomofoModeIdentifier; _chineseConversionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kChineseConversionEnabledKey]; _previousChineseConversionEnabledStatus = _chineseConversionEnabled; @@ -215,30 +221,6 @@ public: NSMenuItem *preferenceMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"vChewing Preferences", @"") action:@selector(showPreferences:) keyEquivalent:@""]; [menu addItem:preferenceMenuItem]; - // If Option key is pressed, show the learning-related menu - - #if DEBUG - //I think the following line is 10.6+ specific - if ([[NSEvent class] respondsToSelector:@selector(modifierFlags)] && ([NSEvent modifierFlags] & NSAlternateKeyMask)) { - - BOOL learningEnabled = ![[NSUserDefaults standardUserDefaults] boolForKey:kDisableUserCandidateSelectionLearning]; - - NSMenuItem *learnMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Enable Selection Learning", @"") action:@selector(toggleLearning:) keyEquivalent:@""]; - learnMenuItem.state = learningEnabled ? NSControlStateValueOn : NSControlStateValueOff; - [menu addItem:learnMenuItem]; - - if (learningEnabled) { - NSString *clearMenuItemTitle = [NSString stringWithFormat:NSLocalizedString(@"Clear Learning Dictionary (%ju Items)", @""), (uintmax_t)[gCandidateLearningDictionary count]]; - NSMenuItem *clearMenuItem = [[NSMenuItem alloc] initWithTitle:clearMenuItemTitle action:@selector(clearLearningDictionary:) keyEquivalent:@""]; - [menu addItem:clearMenuItem]; - - - NSMenuItem *dumpMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Dump Learning Data to Console", @"") action:@selector(dumpLearningDictionary:) keyEquivalent:@""]; - [menu addItem:dumpMenuItem]; - } - } - #endif //DEBUG - NSMenuItem *chineseConversionMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Chinese Conversion", @"") action:@selector(toggleChineseConverter:) keyEquivalent:@"K"]; chineseConversionMenuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand; chineseConversionMenuItem.state = _chineseConversionEnabled ? NSControlStateValueOn : NSControlStateValueOff; @@ -700,16 +682,15 @@ public: // then walk the lattice [self popOverflowComposingTextAndWalk:client]; - // see if we need to override the selection if a learned one exists - if (![[NSUserDefaults standardUserDefaults] boolForKey:kDisableUserCandidateSelectionLearning]) { - NSString *trigram = [self neighborTrigramString]; - - // Lookup from the user dict to see if the trigram fit or not - NSString *overrideCandidateString = [gCandidateLearningDictionary objectForKey:trigram]; - if (overrideCandidateString) { - [self candidateSelected:(NSAttributedString *)overrideCandidateString]; - } - } + // get user override model suggestion + string overrideValue = + _uom->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]); + if (!overrideValue.empty()) { + size_t cursorIndex = [self actualCandidateCursorIndex]; + vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); + double highestScore = FindHighestScore(nodes, kEpsilon); + _builder->grid().overrideNodeScoreForSelectedCandidate(cursorIndex, overrideValue, highestScore); + } // then update the text _bpmfReadingBuffer->clear(); @@ -1250,78 +1231,6 @@ public: return cursorIndex; } -- (NSString *)neighborTrigramString -{ - // gather the "trigram" for user candidate selection learning - - NSMutableArray *termArray = [NSMutableArray array]; - - size_t cursorIndex = [self actualCandidateCursorIndex]; - vector nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex); - - const Node* prev = 0; - const Node* current = 0; - const Node* next = 0; - - size_t wni = 0; - size_t wnc = _walkedNodes.size(); - size_t accuSpanningLength = 0; - for (wni = 0; wni < wnc; wni++) { - NodeAnchor& anchor = _walkedNodes[wni]; - if (!anchor.node) { - continue; - } - - accuSpanningLength += anchor.spanningLength; - if (accuSpanningLength >= cursorIndex) { - prev = current; - current = anchor.node; - break; - } - - current = anchor.node; - } - - if (wni + 1 < wnc) { - next = _walkedNodes[wni + 1].node; - } - - string term; - if (prev) { - term = prev->currentKeyValue().key; - [termArray addObject:[NSString stringWithUTF8String:term.c_str()]]; - } - - if (current) { - term = current->currentKeyValue().key; - [termArray addObject:[NSString stringWithUTF8String:term.c_str()]]; - } - - if (next) { - term = next->currentKeyValue().key; - [termArray addObject:[NSString stringWithUTF8String:term.c_str()]]; - } - - return [termArray componentsJoinedByString:@"-"]; -} - -- (void)_performDeferredSaveUserCandidatesDictionary -{ - BOOL __unused success = [gCandidateLearningDictionary writeToFile:gUserCandidatesDictionaryPath atomically:YES]; -} - -- (void)saveUserCandidatesDictionary -{ - if (!gUserCandidatesDictionaryPath) { - return; - } - - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_performDeferredSaveUserCandidatesDictionary) object:nil]; - - // TODO: Const-ize the delay - [self performSelector:@selector(_performDeferredSaveUserCandidatesDictionary) withObject:nil afterDelay:5.0]; -} - - (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client { // set the candidate panel style @@ -1420,13 +1329,6 @@ public: [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } -- (void)toggleLearning:(id)sender -{ - BOOL toggle = ![[NSUserDefaults standardUserDefaults] boolForKey:kDisableUserCandidateSelectionLearning]; - - [[NSUserDefaults standardUserDefaults] setBool:toggle forKey:kDisableUserCandidateSelectionLearning]; -} - - (void)toggleChineseConverter:(id)sender { _chineseConversionEnabled = !_chineseConversionEnabled; @@ -1434,17 +1336,6 @@ public: [[NSNotificationCenter defaultCenter] postNotificationName:@"ChineseConversionStatusChanged" object:nil]; } -- (void)clearLearningDictionary:(id)sender -{ - [gCandidateLearningDictionary removeAllObjects]; - [self _performDeferredSaveUserCandidatesDictionary]; -} - -- (void)dumpLearningDictionary:(id)sender -{ - NSLog(@"%@", gCandidateLearningDictionary); -} - - (NSUInteger)candidateCountForController:(VTCandidateController *)controller { return [_candidates count]; @@ -1462,15 +1353,10 @@ public: // candidate selected, override the node with selection string selectedValue = [[_candidates objectAtIndex:index] UTF8String]; - if (![[NSUserDefaults standardUserDefaults] boolForKey:kDisableUserCandidateSelectionLearning]) { - NSString *trigram = [self neighborTrigramString]; - NSString *selectedNSString = [NSString stringWithUTF8String:selectedValue.c_str()]; - [gCandidateLearningDictionary setObject:selectedNSString forKey:trigram]; - [self saveUserCandidatesDictionary]; - } - size_t cursorIndex = [self actualCandidateCursorIndex]; _builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue); + + _uom->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]); [_candidates removeAllObjects]; @@ -1512,57 +1398,4 @@ void LTLoadLanguageModel() { LTLoadLanguageModelFile(@"data", gLanguageModel); LTLoadLanguageModelFile(@"data-chs", gLanguageModelSimpBopomofo); - - - // initialize the singleton learning dictionary - // putting singleton in @synchronized is the standard way in Objective-C - // to avoid race condition - gCandidateLearningDictionary = [[NSMutableDictionary alloc] init]; - - // the first instance is also responsible for loading the dictionary - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES); - if (![paths count]) { - NSLog(@"Fatal error: cannot find Applicaiton Support directory."); - return; - } - - NSString *appSupportPath = [paths objectAtIndex:0]; - NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"vChewing"]; - - BOOL isDir = NO; - BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:userDictPath isDirectory:&isDir]; - - if (exists) { - if (!isDir) { - NSLog(@"Fatal error: Path '%@' is not a directory", userDictPath); - return; - } - } - else { - NSError *error = nil; - BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:userDictPath withIntermediateDirectories:YES attributes:nil error:&error]; - if (!success) { - NSLog(@"Failed to create directory '%@', error: %@", userDictPath, error); - return; - } - } - - // TODO: Change this - NSString *userDictFile = [userDictPath stringByAppendingPathComponent:@"UserCandidatesCache.plist"]; - gUserCandidatesDictionaryPath = userDictFile; - - exists = [[NSFileManager defaultManager] fileExistsAtPath:userDictFile isDirectory:&isDir]; - if (exists && !isDir) { - NSData *data = [NSData dataWithContentsOfFile:userDictFile]; - if (!data) { - return; - } - - id plist = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:NULL]; - if (plist && [plist isKindOfClass:[NSDictionary class]]) { - [gCandidateLearningDictionary setDictionary:(NSDictionary *)plist]; - NSLog(@"User dictionary read, item count: %ju", (uintmax_t)[gCandidateLearningDictionary count]); - } - } - } -- Gitee From 0ef8db64064edcc7a47aa7f43cb19be4be95e7b6 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Thu, 6 Jan 2022 12:43:32 +0800 Subject: [PATCH 014/163] New About Window // dev phase 1. --- Source/AppDelegate.m | 6 + Source/Base.lproj/MainMenu.xib | 2 +- Source/Base.lproj/frmAboutWindow.xib | 209 ++++++++++++++++++ .../AboutBanner.imageset/AboutBanner.png | Bin 0 -> 19332 bytes .../AboutBanner.imageset/AboutBanner@2x.png | Bin 0 -> 30050 bytes .../AboutBanner.imageset/AboutBanner@3x.png | Bin 0 -> 57589 bytes .../AboutBanner.imageset/Contents.json | 23 ++ .../AppIcon.appiconset/192X192.png | Bin 32613 -> 0 bytes Source/InputMethodController.mm | 5 +- Source/Installer/en.lproj/InfoPlist.strings | 2 +- .../Installer/zh-Hans.lproj/InfoPlist.strings | 2 +- Source/Installer/zh-Hans.lproj/MainMenu.xib | 16 +- .../Installer/zh-Hant.lproj/InfoPlist.strings | 2 +- Source/Installer/zh-Hant.lproj/MainMenu.xib | 12 +- Source/en.lproj/InfoPlist.strings | 3 +- Source/en.lproj/MITLicense.txt | 5 + Source/en.lproj/frmAboutWindow.strings | 27 +++ Source/frmAboutWindow.h | 38 ++++ Source/frmAboutWindow.m | 75 +++++++ Source/vChewing-Info.plist | 4 +- Source/zh-Hans.lproj/InfoPlist.strings | 3 +- Source/zh-Hans.lproj/MITLicense.txt | 5 + Source/zh-Hans.lproj/MainMenu.xib | 7 +- Source/zh-Hans.lproj/frmAboutWindow.strings | 27 +++ Source/zh-Hant.lproj/InfoPlist.strings | 3 +- Source/zh-Hant.lproj/MITLicense.txt | 5 + Source/zh-Hant.lproj/frmAboutWindow.strings | 27 +++ vChewing.xcodeproj/project.pbxproj | 45 +++- 28 files changed, 524 insertions(+), 29 deletions(-) create mode 100644 Source/Base.lproj/frmAboutWindow.xib create mode 100644 Source/Images/Images.xcassets/AboutBanner.imageset/AboutBanner.png create mode 100644 Source/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@2x.png create mode 100644 Source/Images/Images.xcassets/AboutBanner.imageset/AboutBanner@3x.png create mode 100644 Source/Images/Images.xcassets/AboutBanner.imageset/Contents.json delete mode 100644 Source/Images/Images.xcassets/AppIcon.appiconset/192X192.png create mode 100644 Source/en.lproj/MITLicense.txt create mode 100644 Source/en.lproj/frmAboutWindow.strings create mode 100644 Source/frmAboutWindow.h create mode 100644 Source/frmAboutWindow.m create mode 100644 Source/zh-Hans.lproj/MITLicense.txt create mode 100644 Source/zh-Hans.lproj/frmAboutWindow.strings create mode 100644 Source/zh-Hant.lproj/MITLicense.txt create mode 100644 Source/zh-Hant.lproj/frmAboutWindow.strings diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index 46d1ed3f4..5e9369c01 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -35,6 +35,7 @@ #import "AppDelegate.h" #import "OVNonModalAlertWindowController.h" #import "PreferencesWindowController.h" +#import "frmAboutWindow.h" extern void LTLoadLanguageModel(void); @@ -273,4 +274,9 @@ static const NSTimeInterval kTimeoutInterval = 60.0; return YES; } +- (IBAction) about:(id)sender { + // Show the window: + [[frmAboutWindow defaultController].window orderFront:self]; +} + @end diff --git a/Source/Base.lproj/MainMenu.xib b/Source/Base.lproj/MainMenu.xib index 4c79f2b95..b834ac493 100644 --- a/Source/Base.lproj/MainMenu.xib +++ b/Source/Base.lproj/MainMenu.xib @@ -20,7 +20,7 @@ - + diff --git a/Source/Base.lproj/frmAboutWindow.xib b/Source/Base.lproj/frmAboutWindow.xib new file mode 100644 index 000000000..50d365588 --- /dev/null +++ b/Source/Base.lproj/frmAboutWindow.xib @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al. +vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen. + + + + + + + + + + + + + + + + + + + + + DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Images/Images.xcassets/AboutBanner.imageset/AboutBanner.png b/Source/Images/Images.xcassets/AboutBanner.imageset/AboutBanner.png new file mode 100644 index 0000000000000000000000000000000000000000..b852e1e3459c27193daeaf51aac71336f41e2890 GIT binary patch literal 19332 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 x1=?j2Z=ufAm}Fyb`$s4%<37BJC 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

cMD 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 diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 64e009bce..7fa194495 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -39,10 +39,12 @@ #import "OVStringHelper.h" #import "OVUTF8Helper.h" #import "AppDelegate.h" +#import "frmAboutWindow.h" #import "VTHorizontalCandidateController.h" #import "VTVerticalCandidateController.h" #import "vChewing-Swift.h" + //@import SwiftUI; // C++ namespace usages @@ -1325,8 +1327,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)showAbout:(id)sender { - [[NSApplication sharedApplication] orderFrontStandardAboutPanel:sender]; - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; + [[frmAboutWindow defaultController].window orderFront:sender]; } - (void)toggleChineseConverter:(id)sender diff --git a/Source/Installer/en.lproj/InfoPlist.strings b/Source/Installer/en.lproj/InfoPlist.strings index 080e9bd08..657c8a41f 100644 --- a/Source/Installer/en.lproj/InfoPlist.strings +++ b/Source/Installer/en.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ /* Localized versions of Info.plist keys */ CFBundleName = "Install vChewing"; -NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; +NSHumanReadableCopyright = "Copyright © 2011-2022 OpenVanilla Project (Mengjuei Hsieh et al.)\nAll Rights Reserved."; diff --git a/Source/Installer/zh-Hans.lproj/InfoPlist.strings b/Source/Installer/zh-Hans.lproj/InfoPlist.strings index 7f7803f6c..3338b4099 100644 --- a/Source/Installer/zh-Hans.lproj/InfoPlist.strings +++ b/Source/Installer/zh-Hans.lproj/InfoPlist.strings @@ -1,5 +1,5 @@ /* Localized versions of Info.plist keys */ CFBundleName = "安装威注音"; -NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; +NSHumanReadableCopyright = "Copyright © 2011-2022 OpenVanilla Project (Mengjuei Hsieh et al.)\nAll Rights Reserved."; diff --git a/Source/Installer/zh-Hans.lproj/MainMenu.xib b/Source/Installer/zh-Hans.lproj/MainMenu.xib index d90801fde..ef091d7f2 100644 --- a/Source/Installer/zh-Hans.lproj/MainMenu.xib +++ b/Source/Installer/zh-Hans.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -87,7 +87,7 @@ - + @@ -97,14 +97,14 @@ - + - + @@ -148,7 +148,7 @@ Gw - + @@ -157,7 +157,7 @@ Gw - + @@ -183,7 +183,7 @@ Gw - + diff --git a/Source/Installer/zh-Hant.lproj/InfoPlist.strings b/Source/Installer/zh-Hant.lproj/InfoPlist.strings index f034b1a51..bdfa843b7 100644 --- a/Source/Installer/zh-Hant.lproj/InfoPlist.strings +++ b/Source/Installer/zh-Hant.lproj/InfoPlist.strings @@ -1,5 +1,5 @@ /* Localized versions of Info.plist keys */ CFBundleName = "安裝威注音"; -NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; +NSHumanReadableCopyright = "Copyright © 2011-2022 OpenVanilla Project (Mengjuei Hsieh et al.)\nAll Rights Reserved."; diff --git a/Source/Installer/zh-Hant.lproj/MainMenu.xib b/Source/Installer/zh-Hant.lproj/MainMenu.xib index bb333e832..1adabe610 100644 --- a/Source/Installer/zh-Hant.lproj/MainMenu.xib +++ b/Source/Installer/zh-Hant.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -87,7 +87,7 @@ - + @@ -97,14 +97,14 @@ - + - + @@ -183,7 +183,7 @@ Gw - + diff --git a/Source/en.lproj/InfoPlist.strings b/Source/en.lproj/InfoPlist.strings index a5c0514d2..09f06fe87 100644 --- a/Source/en.lproj/InfoPlist.strings +++ b/Source/en.lproj/InfoPlist.strings @@ -1,5 +1,6 @@ CFBundleName = "vChewing"; CFBundleDisplayName = "vChewing"; -NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; "org.openvanilla.inputmethod.vChewing.Bopomofo" = "vChewing-CHT"; "org.openvanilla.inputmethod.vChewing.SimpBopomofo" = "vChewing-CHS"; +CFEULAContent = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."; diff --git a/Source/en.lproj/MITLicense.txt b/Source/en.lproj/MITLicense.txt new file mode 100644 index 000000000..3488b7d02 --- /dev/null +++ b/Source/en.lproj/MITLicense.txt @@ -0,0 +1,5 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Source/en.lproj/frmAboutWindow.strings b/Source/en.lproj/frmAboutWindow.strings new file mode 100644 index 000000000..432fbaecf --- /dev/null +++ b/Source/en.lproj/frmAboutWindow.strings @@ -0,0 +1,27 @@ + +/* Class = "NSTextFieldCell"; title = "MIT License:"; ObjectID = "lblMITLicense"; */ +"lblMITLicense.title" = "MIT License:"; + +/* Class = "NSTextFieldCell"; title = "version"; ObjectID = "lblVersionString"; */ +// "lblVersionString.title" = "version"; + +/* Class = "NSWindow"; title = "About vChewing for macOS"; ObjectID = "ttlAboutWindow"; */ +"ttlAboutWindow.title" = "About vChewing for macOS"; + +/* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "lblProjectDescription"; */ +"lblProjectDescription.title" = "Derived from OpenVanilla McBopopmofo Project."; + +/* Class = "NSButtonCell"; title = "OK"; ObjectID = "btnConfirm"; */ +"btnConfirm.title" = "OK"; + +/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "lblAppTitle"; */ +"lblAppTitle.title" = "vChewing for macOS"; + +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "lblDisclaimer"; */ +"lblDisclaimer.title" = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; + +/* Class = "NSTextFieldCell"; title = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; ObjectID = "lblCopyright"; */ +// "lblCopyright.title" = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; + +/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "lblCredits"; */ +"lblCredits.title" = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen."; diff --git a/Source/frmAboutWindow.h b/Source/frmAboutWindow.h new file mode 100644 index 000000000..3c2c2d73b --- /dev/null +++ b/Source/frmAboutWindow.h @@ -0,0 +1,38 @@ +// +// frmAboutWindow.h +// Tile Map Editor +// +// Created & Original Rights by Nicolás Miari on 2016/02/11. +// Patched by Shiki Suen for the vChewing Project. +// Released under MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#import + +@interface frmAboutWindow : NSWindowController + ++ (instancetype) defaultController; + +@property (nonatomic) IBOutlet NSTextField *appNameLabel; +@property (nonatomic) IBOutlet NSTextField *appVersionLabel; +@property (nonatomic) IBOutlet NSTextField *appCopyrightLabel; +@property (nonatomic) IBOutlet NSTextView *appEULAContent; + +@end diff --git a/Source/frmAboutWindow.m b/Source/frmAboutWindow.m new file mode 100644 index 000000000..4b608a9d0 --- /dev/null +++ b/Source/frmAboutWindow.m @@ -0,0 +1,75 @@ +// +// frmAboutWindow.m +// Tile Map Editor +// +// Created & Original Rights by Nicolás Miari on 2016/02/11. +// Patched by Shiki Suen for the vChewing Project. +// Released under MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#import "frmAboutWindow.h" + + +@implementation frmAboutWindow +@synthesize appNameLabel; +@synthesize appVersionLabel; +@synthesize appCopyrightLabel; +@synthesize appEULAContent; + ++ (instancetype) defaultController { + + static id staticInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + staticInstance = [[self alloc] init]; + }); + + return staticInstance; +} + + +#pragma mark - Initialization + + +- (instancetype) init { + return [super initWithWindowNibName:@"frmAboutWindow" owner:self]; +} + + +#pragma mark - NSWindowController + + +- (void) windowDidLoad { + + [super windowDidLoad]; + + NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary]; + + self.appNameLabel.stringValue = [infoDictionary objectForKey:@"CFBundleName"]; + self.appVersionLabel.stringValue = [infoDictionary objectForKey:@"CFBundleShortVersionString"]; + self.appCopyrightLabel.stringValue = [infoDictionary objectForKey:@"NSHumanReadableCopyright"]; + self.appEULAContent.string = [infoDictionary objectForKey:@"CFEULAContent"]; + + // If you add more custom subviews to display additional information about + // your app, configure them here +} + +@end diff --git a/Source/vChewing-Info.plist b/Source/vChewing-Info.plist index 7e5e75103..513261300 100644 --- a/Source/vChewing-Info.plist +++ b/Source/vChewing-Info.plist @@ -2,6 +2,8 @@ + CFEULAContent + License texts used in the customized about window. CFBundleDevelopmentRegion English CFBundleExecutable @@ -104,7 +106,7 @@ LSUIElement NSHumanReadableCopyright - Copyright © 2011-2021 Mengjuei Hsieh et al. + © 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project. NSMainNibFile MainMenu NSPrincipalClass diff --git a/Source/zh-Hans.lproj/InfoPlist.strings b/Source/zh-Hans.lproj/InfoPlist.strings index 512bfd976..4de42942f 100644 --- a/Source/zh-Hans.lproj/InfoPlist.strings +++ b/Source/zh-Hans.lproj/InfoPlist.strings @@ -1,5 +1,6 @@ CFBundleName = "威注音"; CFBundleDisplayName = "威注音"; -NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; "org.openvanilla.inputmethod.vChewing.Bopomofo" = "威註音"; "org.openvanilla.inputmethod.vChewing.SimpBopomofo" = "威注音"; +CFEULAContent = "软件的著作权利人依此麻理授权条款,将其对于软件的著作权利授权释出,只要使用者践履以下二项麻理授权条款叙明的义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用的权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面的应用,而散布程式之人、更可将上述权利传递予其后收受程式的后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款的义务性规定,则其对程式亦享有与前手运用范围相同的同一权利。\n\n散布此一软件程序者,须将本条款其上的「著作权声明」及以下的「免责声明」,内嵌于软件程序及其重制作品的实体之中。\n\n因麻理软件程序的授权模式乃是无偿提供,是以在现行法律的架构下可以主张合理的免除担保责任。麻理软件的著作权人或任何的后续散布者,对于其所散布的麻理软件程序皆不负任何形式上实质上的担保责任,明示亦或隐喻、商业利用性亦或特定目的使用性,这些均不在保障之列。利用麻理软件程序的所有风险均由使用者自行担负。假如所使用的麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要的服务支出。麻理软件程序的著作权人不负任何形式上实质上的担保责任,无论任何一般的、特殊的、偶发的、因果关系式的损害,或是麻理软件程序的不适用性,均须由使用者自行负担。"; diff --git a/Source/zh-Hans.lproj/MITLicense.txt b/Source/zh-Hans.lproj/MITLicense.txt new file mode 100644 index 000000000..8823b7808 --- /dev/null +++ b/Source/zh-Hans.lproj/MITLicense.txt @@ -0,0 +1,5 @@ +軟體的著作權利人依此麻理授權條款,將其對於軟體的著作權利授權釋出,只要使用者踐履以下二項麻理授權條款敘明的義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用的權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面的應用,而散布程式之人、更可將上述權利傳遞予其後收受程式的後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款的義務性規定,則其對程式亦享有與前手運用範圍相同的同一權利。 + +散布此一軟體程式者,須將本條款其上的「著作權聲明」及以下的「免責聲明」,內嵌於軟體程式及其重製作品的實體之中。 + +因麻理軟體程式的授權模式乃是無償提供,是以在現行法律的架構下可以主張合理的免除擔保責任。麻理軟體的著作權人或任何的後續散布者,對於其所散布的麻理軟體程式皆不負任何形式上實質上的擔保責任,明示亦或隱喻、商業利用性亦或特定目的使用性,這些均不在保障之列。利用麻理軟體程式的所有風險均由使用者自行擔負。假如所使用的麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要的服務支出。麻理軟體程式的著作權人不負任何形式上實質上的擔保責任,無論任何一般的、特殊的、偶發的、因果關係式的損害,或是麻理軟體程式的不適用性,均須由使用者自行負擔。 diff --git a/Source/zh-Hans.lproj/MainMenu.xib b/Source/zh-Hans.lproj/MainMenu.xib index 4c79f2b95..ce657a67b 100644 --- a/Source/zh-Hans.lproj/MainMenu.xib +++ b/Source/zh-Hans.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -11,7 +11,7 @@ - + @@ -288,6 +288,7 @@ +

diff --git a/Source/zh-Hans.lproj/frmAboutWindow.strings b/Source/zh-Hans.lproj/frmAboutWindow.strings new file mode 100644 index 000000000..017c4e33f --- /dev/null +++ b/Source/zh-Hans.lproj/frmAboutWindow.strings @@ -0,0 +1,27 @@ + +/* Class = "NSTextFieldCell"; title = "MIT License:"; ObjectID = "lblMITLicense"; */ +"lblMITLicense.title" = "麻理许可协议 (MIT License):"; + +/* Class = "NSTextFieldCell"; title = "version"; ObjectID = "lblVersionString"; */ +// "lblVersionString.title" = "版本"; + +/* Class = "NSWindow"; title = "About vChewing for macOS"; ObjectID = "ttlAboutWindow"; */ +"ttlAboutWindow.title" = "关于 macOS 版「威注音输入法」"; + +/* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "lblProjectDescription"; */ +"lblProjectDescription.title" = "该专案由 OpenVanilla 小麦注音专案衍生而来。"; + +/* Class = "NSButtonCell"; title = "OK"; ObjectID = "btnConfirm"; */ +"btnConfirm.title" = "确定"; + +/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "lblAppTitle"; */ +"lblAppTitle.title" = "vChewing for macOS"; + +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "lblDisclaimer"; */ +"lblDisclaimer.title" = "免责声明:威注音专案对小麦注音官方专案内赠的小麦注音原版词库内容不负任何责任。威注音输入法专用的威注音词库已依照《中华人民共和国国家安全法》与《中华人民共和国反分裂国家法》做过合规处理。"; + +/* Class = "NSTextFieldCell"; title = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; ObjectID = "lblCopyright"; */ +// "lblCopyright.title" = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; + +/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "lblCredits"; */ +"lblCredits.title" = "小麦注音引擎研发:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang (Zonble), 等。\n威注音 macOS 程式研发协力:Hiraku Wang。\n威注音词库维护:孙志贵 (Shiki Suen)。"; diff --git a/Source/zh-Hant.lproj/InfoPlist.strings b/Source/zh-Hant.lproj/InfoPlist.strings index 512bfd976..770cbb8aa 100644 --- a/Source/zh-Hant.lproj/InfoPlist.strings +++ b/Source/zh-Hant.lproj/InfoPlist.strings @@ -1,5 +1,6 @@ CFBundleName = "威注音"; CFBundleDisplayName = "威注音"; -NSHumanReadableCopyright = "Copyright © 2011-2021 Mengjuei Hsieh et al.\nAll Rights Reserved."; +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; "org.openvanilla.inputmethod.vChewing.Bopomofo" = "威註音"; "org.openvanilla.inputmethod.vChewing.SimpBopomofo" = "威注音"; +CFEULAContent = "軟體的著作權利人依此麻理授權條款,將其對於軟體的著作權利授權釋出,只要使用者踐履以下二項麻理授權條款敘明的義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用的權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面的應用,而散布程式之人、更可將上述權利傳遞予其後收受程式的後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款的義務性規定,則其對程式亦享有與前手運用範圍相同的同一權利。\n\n散布此一軟體程式者,須將本條款其上的「著作權聲明」及以下的「免責聲明」,內嵌於軟體程式及其重製作品的實體之中。\n\n因麻理軟體程式的授權模式乃是無償提供,是以在現行法律的架構下可以主張合理的免除擔保責任。麻理軟體的著作權人或任何的後續散布者,對於其所散布的麻理軟體程式皆不負任何形式上實質上的擔保責任,明示亦或隱喻、商業利用性亦或特定目的使用性,這些均不在保障之列。利用麻理軟體程式的所有風險均由使用者自行擔負。假如所使用的麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要的服務支出。麻理軟體程式的著作權人不負任何形式上實質上的擔保責任,無論任何一般的、特殊的、偶發的、因果關係式的損害,或是麻理軟體程式的不適用性,均須由使用者自行負擔。"; diff --git a/Source/zh-Hant.lproj/MITLicense.txt b/Source/zh-Hant.lproj/MITLicense.txt new file mode 100644 index 000000000..403c88875 --- /dev/null +++ b/Source/zh-Hant.lproj/MITLicense.txt @@ -0,0 +1,5 @@ +软件的著作权利人依此麻理授权条款,将其对于软件的著作权利授权释出,只要使用者践履以下二项麻理授权条款叙明的义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用的权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面的应用,而散布程式之人、更可将上述权利传递予其后收受程式的后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款的义务性规定,则其对程式亦享有与前手运用范围相同的同一权利。 + +散布此一软件程序者,须将本条款其上的「著作权声明」及以下的「免责声明」,内嵌于软件程序及其重制作品的实体之中。 + +因麻理软件程序的授权模式乃是无偿提供,是以在现行法律的架构下可以主张合理的免除担保责任。麻理软件的著作权人或任何的后续散布者,对于其所散布的麻理软件程序皆不负任何形式上实质上的担保责任,明示亦或隐喻、商业利用性亦或特定目的使用性,这些均不在保障之列。利用麻理软件程序的所有风险均由使用者自行担负。假如所使用的麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要的服务支出。麻理软件程序的著作权人不负任何形式上实质上的担保责任,无论任何一般的、特殊的、偶发的、因果关系式的损害,或是麻理软件程序的不适用性,均须由使用者自行负担。 diff --git a/Source/zh-Hant.lproj/frmAboutWindow.strings b/Source/zh-Hant.lproj/frmAboutWindow.strings new file mode 100644 index 000000000..2d49020b6 --- /dev/null +++ b/Source/zh-Hant.lproj/frmAboutWindow.strings @@ -0,0 +1,27 @@ + +/* Class = "NSTextFieldCell"; title = "MIT License:"; ObjectID = "lblMITLicense"; */ +"lblMITLicense.title" = "麻理授權合約 (MIT License):"; + +/* Class = "NSTextFieldCell"; title = "version"; ObjectID = "lblVersionString"; */ +// "lblVersionString.title" = "版本"; + +/* Class = "NSWindow"; title = "About vChewing for macOS"; ObjectID = "ttlAboutWindow"; */ +"ttlAboutWindow.title" = "關於 macOS 版「威注音輸入法」"; + +/* Class = "NSTextFieldCell"; title = "Derived from OpenVanilla McBopopmofo Project."; ObjectID = "lblProjectDescription"; */ +"lblProjectDescription.title" = "該專案由 OpenVanilla 小麥注音專案衍生而來。"; + +/* Class = "NSButtonCell"; title = "OK"; ObjectID = "btnConfirm"; */ +"btnConfirm.title" = "確定"; + +/* Class = "NSTextFieldCell"; title = "vChewing for macOS"; ObjectID = "lblAppTitle"; */ +"lblAppTitle.title" = "vChewing for macOS"; + +/* Class = "NSTextFieldCell"; title = "DISCLAIMER: The vChewing project is not responsible for the phrase database shipped in the original McBopomofo project. Everything in the vChewing phrase database has been confirmed lawful to the Anti-Secession Law and the National Security Law of the PRC."; ObjectID = "lblDisclaimer"; */ +"lblDisclaimer.title" = "免責聲明:威注音專案對小麥注音官方專案內贈的小麥注音原版詞庫內容不負任何責任。威注音輸入法專用的威注音詞庫已依照《中華人民共和國國家安全法》與《中華人民共和國反分裂國家法》做過合規處理。"; + +/* Class = "NSTextFieldCell"; title = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; ObjectID = "lblCopyright"; */ +// "lblCopyright.title" = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; + +/* Class = "NSTextFieldCell"; title = "McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.\nvChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen."; ObjectID = "lblCredits"; */ +"lblCredits.title" = "小麥注音引擎研發:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang (Zonble), 等。\n威注音 macOS 程式研發協力:Hiraku Wang。\n威注音詞庫維護:孫志貴 (Shiki Suen)。"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index c9317aeb0..3e064140b 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -9,7 +9,11 @@ /* Begin PBXBuildFile section */ 5B000FC3278495AD004F02AC /* SimpBopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 5B000FC1278495AD004F02AC /* SimpBopomofo.tiff */; }; 5B000FC4278495AD004F02AC /* SimpBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 5B000FC2278495AD004F02AC /* SimpBopomofo@2x.tiff */; }; + 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */; }; + 5B58E87F278413E7003EA2AD /* MITLicense.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5B58E87D278413E7003EA2AD /* MITLicense.txt */; }; 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BC3FB82278492DE0022E99A /* data-chs.txt */; }; + 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */; }; + 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */; }; 6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0421A615FEF3F50061ED63 /* FastLM.cpp */; }; 6A0D4EA715FC0D2D00ABF4B3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A0D4EA615FC0D2D00ABF4B3 /* Cocoa.framework */; }; 6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */; }; @@ -87,6 +91,12 @@ 5B9781D82763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "Source/zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 5B9781D92763850700897999 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.xib"; sourceTree = ""; }; 5BC3FB82278492DE0022E99A /* data-chs.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-chs.txt"; sourceTree = ""; }; + 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = frmAboutWindow.h; sourceTree = ""; }; + 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = frmAboutWindow.m; sourceTree = ""; }; + 5BF4A70327844DD0007DC6E7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmAboutWindow.xib; sourceTree = ""; }; + 5BF4A70527844DD2007DC6E7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmAboutWindow.strings; sourceTree = ""; }; + 5BF4A70727844DD3007DC6E7 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/frmAboutWindow.strings"; sourceTree = ""; }; + 5BF4A70A278451A6007DC6E7 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/frmAboutWindow.strings"; sourceTree = ""; }; 6A0421A615FEF3F50061ED63 /* FastLM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FastLM.cpp; sourceTree = ""; }; 6A0421A715FEF3F50061ED63 /* FastLM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FastLM.h; sourceTree = ""; }; 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewing.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -268,6 +278,7 @@ 6A0D4EC215FC0D3C00ABF4B3 /* Source */ = { isa = PBXGroup; children = ( + 5B58E87D278413E7003EA2AD /* MITLicense.txt */, 6A0D4ED815FC0DA600ABF4B3 /* CandidateUI */, 6A38BBDD15FC115800A8A51F /* Data */, 6A0D4F1215FC0EB100ABF4B3 /* Engine */, @@ -275,6 +286,8 @@ 6A0D4F4715FC0EB900ABF4B3 /* Resources */, 6A0D4EC315FC0D6400ABF4B3 /* AppDelegate.h */, 6A0D4EC415FC0D6400ABF4B3 /* AppDelegate.m */, + 5BF4A6FB27844738007DC6E7 /* frmAboutWindow.h */, + 5BF4A6FC27844738007DC6E7 /* frmAboutWindow.m */, 6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */, 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, 6A0D4EC815FC0D6400ABF4B3 /* main.m */, @@ -287,8 +300,8 @@ 6A0D4ECC15FC0D6400ABF4B3 /* PreferencesWindowController.m */, D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */, D427A9BF25ED28CC005D43E0 /* vChewing-Bridging-Header.h */, - 6AE30A471F7F40B7008735BD /* UserOverrideModel.cpp */, - 6AE30A481F7F40B7008735BD /* UserOverrideModel.h */, + 5B42B63E27876FDC00BB9B9F /* UserOverrideModel.cpp */, + 5B42B63F27876FDC00BB9B9F /* UserOverrideModel.h */, ); path = Source; sourceTree = ""; @@ -405,6 +418,7 @@ 6A0D4F4715FC0EB900ABF4B3 /* Resources */ = { isa = PBXGroup; children = ( + 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */, 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */, 6A0D4EEE15FC0DA600ABF4B3 /* Images */, 6A0D4EF515FC0DA600ABF4B3 /* vChewing-Info.plist */, @@ -581,7 +595,9 @@ 6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */, 6A38BC1515FC117A00A8A51F /* data.txt in Resources */, 6AFF97F2253B299E007F1C49 /* OVNonModalAlertWindowController.xib in Resources */, + 5B58E87F278413E7003EA2AD /* MITLicense.txt in Resources */, 6A187E2616004C5900466B2E /* MainMenu.xib in Resources */, + 5BF4A70027844DC5007DC6E7 /* frmAboutWindow.xib in Resources */, 5BC3FB83278492DE0022E99A /* data-chs.txt in Resources */, 5B000FC4278495AD004F02AC /* SimpBopomofo@2x.tiff in Resources */, ); @@ -631,7 +647,9 @@ 6A0D4ED015FC0D6400ABF4B3 /* AppDelegate.m in Sources */, 6A0D4ED215FC0D6400ABF4B3 /* InputMethodController.mm in Sources */, 6A0D4ED315FC0D6400ABF4B3 /* main.m in Sources */, + 5BF4A6FE27844738007DC6E7 /* frmAboutWindow.m in Sources */, 6A0D4ED415FC0D6400ABF4B3 /* OVInputSourceHelper.m in Sources */, + 5B42B64027876FDC00BB9B9F /* UserOverrideModel.cpp in Sources */, 6A0D4ED515FC0D6400ABF4B3 /* PreferencesWindowController.m in Sources */, 6A0D4EFE15FC0DA600ABF4B3 /* VTCandidateController.m in Sources */, 6A0D4EFF15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m in Sources */, @@ -673,6 +691,27 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 5B58E87D278413E7003EA2AD /* MITLicense.txt */ = { + isa = PBXVariantGroup; + children = ( + 5B58E87E278413E7003EA2AD /* en */, + 5B58E880278413EF003EA2AD /* zh-Hans */, + 5B58E881278413F1003EA2AD /* zh-Hant */, + ); + name = MITLicense.txt; + sourceTree = ""; + }; + 5BF4A70227844DC5007DC6E7 /* frmAboutWindow.xib */ = { + isa = PBXVariantGroup; + children = ( + 5BF4A70327844DD0007DC6E7 /* Base */, + 5BF4A70527844DD2007DC6E7 /* en */, + 5BF4A70727844DD3007DC6E7 /* zh-Hant */, + 5BF4A70A278451A6007DC6E7 /* zh-Hans */, + ); + name = frmAboutWindow.xib; + sourceTree = ""; + }; 6A0D4F4815FC0EE100ABF4B3 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -781,6 +820,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; @@ -821,6 +861,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; -- Gitee From 039b9d9a0145b1ee11044f39f2f3245e1bdb4f1a Mon Sep 17 00:00:00 2001 From: Hiraku Date: Mon, 10 Jan 2022 16:23:14 +0800 Subject: [PATCH 015/163] New About Window // dev phase 2. Fix an issue that the About window failed from retrieving and presenting specified data fields from info.plist file. --- Source/AppDelegate.m | 2 +- Source/Base.lproj/frmAboutWindow.xib | 3 +++ Source/InputMethodController.mm | 2 +- Source/frmAboutWindow.h | 3 ++- Source/frmAboutWindow.m | 25 ++++++++++++++++++------- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index 5e9369c01..245441c48 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -276,7 +276,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0; - (IBAction) about:(id)sender { // Show the window: - [[frmAboutWindow defaultController].window orderFront:self]; + [[frmAboutWindow defaultController] showWithSender:self]; } @end diff --git a/Source/Base.lproj/frmAboutWindow.xib b/Source/Base.lproj/frmAboutWindow.xib index 50d365588..bef94b493 100644 --- a/Source/Base.lproj/frmAboutWindow.xib +++ b/Source/Base.lproj/frmAboutWindow.xib @@ -8,6 +8,9 @@ + + + diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 7fa194495..1a9852146 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -1327,7 +1327,7 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)showAbout:(id)sender { - [[frmAboutWindow defaultController].window orderFront:sender]; + [[frmAboutWindow defaultController] showWithSender:sender]; } - (void)toggleChineseConverter:(id)sender diff --git a/Source/frmAboutWindow.h b/Source/frmAboutWindow.h index 3c2c2d73b..c667deb06 100644 --- a/Source/frmAboutWindow.h +++ b/Source/frmAboutWindow.h @@ -3,7 +3,7 @@ // Tile Map Editor // // Created & Original Rights by Nicolás Miari on 2016/02/11. -// Patched by Shiki Suen for the vChewing Project. +// Patched by Hiraku Wang and Shiki Suen for the vChewing Project. // Released under MIT License. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -29,6 +29,7 @@ @interface frmAboutWindow : NSWindowController + (instancetype) defaultController; +- (void) showWithSender:(id)sender; @property (nonatomic) IBOutlet NSTextField *appNameLabel; @property (nonatomic) IBOutlet NSTextField *appVersionLabel; diff --git a/Source/frmAboutWindow.m b/Source/frmAboutWindow.m index 4b608a9d0..e90a969bd 100644 --- a/Source/frmAboutWindow.m +++ b/Source/frmAboutWindow.m @@ -3,7 +3,7 @@ // Tile Map Editor // // Created & Original Rights by Nicolás Miari on 2016/02/11. -// Patched by Shiki Suen for the vChewing Project. +// Patched by Hiraku Wang and Shiki Suen for the vChewing Project. // Released under MIT License. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -61,15 +61,26 @@ [super windowDidLoad]; - NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary]; - - self.appNameLabel.stringValue = [infoDictionary objectForKey:@"CFBundleName"]; - self.appVersionLabel.stringValue = [infoDictionary objectForKey:@"CFBundleShortVersionString"]; - self.appCopyrightLabel.stringValue = [infoDictionary objectForKey:@"NSHumanReadableCopyright"]; - self.appEULAContent.string = [infoDictionary objectForKey:@"CFEULAContent"]; + [self updateInfo]; // If you add more custom subviews to display additional information about // your app, configure them here } +- (void) updateInfo { + NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary]; + NSDictionary* localizedInfoDictionary = [[NSBundle mainBundle] localizedInfoDictionary]; + + self.appNameLabel.stringValue = [localizedInfoDictionary objectForKey:@"CFBundleName"]; + self.appVersionLabel.stringValue = [infoDictionary objectForKey:@"CFBundleShortVersionString"]; + self.appCopyrightLabel.stringValue = [localizedInfoDictionary objectForKey:@"NSHumanReadableCopyright"]; + self.appEULAContent.string = [localizedInfoDictionary objectForKey:@"CFEULAContent"]; +} + +- (void) showWithSender:(id)sender { + // FIXME: updating the strings every time is a temporary workaround + [self updateInfo]; + [self.window orderFront:sender]; +} + @end -- Gitee From 4418440e36d0e2f982f35627799e6f0f9f75d297 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 8 Jan 2022 00:27:47 +0800 Subject: [PATCH 016/163] New About Window // dev phase 3-7. - Fix an issue that the IME crashes when the first time the About window loads. - Make sure the confirm button works even if the NSWindowButtons are not visible. Previously they were disabled, hindering the confirm button from being functional. - Make sure the About Window always gets pushed to the front every time the user clicks the "about..." menu item. - Attempt to let the About window dealloc correctly. --- Source/AppDelegate.h | 3 +++ Source/AppDelegate.m | 13 +++++++++++-- Source/Base.lproj/frmAboutWindow.xib | 23 +++++++++++++---------- Source/InputMethodController.h | 1 + Source/InputMethodController.mm | 5 +++-- Source/frmAboutWindow.m | 17 ++++++++--------- 6 files changed, 39 insertions(+), 23 deletions(-) diff --git a/Source/AppDelegate.h b/Source/AppDelegate.h index 5e3de3014..8825051eb 100644 --- a/Source/AppDelegate.h +++ b/Source/AppDelegate.h @@ -33,6 +33,7 @@ // #import +#import "frmAboutWindow.h" @class PreferencesWindowController; @@ -44,11 +45,13 @@ NSMutableData *_receivingData; NSURL *_updateNextStepURL; PreferencesWindowController *_preferencesWindowController; + frmAboutWindow *_aboutWindowController; } - (void)checkForUpdate; - (void)checkForUpdateForced:(BOOL)forced; - (void)showPreferences; +- (void)showAbout; @property (weak, nonatomic) IBOutlet NSWindow *window; @end diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index 245441c48..0f1a0d5e3 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -55,6 +55,7 @@ static const NSTimeInterval kTimeoutInterval = 60.0; - (void)dealloc { _preferencesWindowController = nil; + _aboutWindowController = nil; _updateCheckConnection = nil; } @@ -274,9 +275,17 @@ static const NSTimeInterval kTimeoutInterval = 60.0; return YES; } +- (void)showAbout { + if (!_aboutWindowController) { + _aboutWindowController = [[frmAboutWindow alloc] initWithWindowNibName:@"frmAboutWindow"]; + } + [[_aboutWindowController window] center]; + [[_aboutWindowController window] orderFrontRegardless]; +} + - (IBAction) about:(id)sender { - // Show the window: - [[frmAboutWindow defaultController] showWithSender:self]; + [(AppDelegate *)[NSApp delegate] showAbout]; + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } @end diff --git a/Source/Base.lproj/frmAboutWindow.xib b/Source/Base.lproj/frmAboutWindow.xib index bef94b493..4940e6a57 100644 --- a/Source/Base.lproj/frmAboutWindow.xib +++ b/Source/Base.lproj/frmAboutWindow.xib @@ -16,10 +16,12 @@ - - + + - + + + @@ -49,16 +51,13 @@ - - - - - + + @@ -67,7 +66,7 @@ - + @@ -103,7 +102,8 @@ McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al. -vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database Maintained by Shiki Suen. +vChewing macOS Development Reinforced by Hiraku Wang. +vChewing Phrase Database Maintained by Shiki Suen. @@ -131,6 +131,9 @@ vChewing macOS Development Reinforced by Hiraku Wang.
vChewing Phrase Database DQ + + + diff --git a/Source/InputMethodController.h b/Source/InputMethodController.h index 7cb01be81..c595c7af7 100644 --- a/Source/InputMethodController.h +++ b/Source/InputMethodController.h @@ -38,6 +38,7 @@ #import "Gramambular.h" #import "FastLM.h" #import "UserOverrideModel.h" +#import "frmAboutWindow.h" @interface vChewingInputMethodController : IMKInputController { diff --git a/Source/InputMethodController.mm b/Source/InputMethodController.mm index 1a9852146..b7e0ca492 100644 --- a/Source/InputMethodController.mm +++ b/Source/InputMethodController.mm @@ -39,7 +39,6 @@ #import "OVStringHelper.h" #import "OVUTF8Helper.h" #import "AppDelegate.h" -#import "frmAboutWindow.h" #import "VTHorizontalCandidateController.h" #import "VTVerticalCandidateController.h" #import "vChewing-Swift.h" @@ -1327,7 +1326,9 @@ static double FindHighestScore(const vector& nodes, double epsilon) - (void)showAbout:(id)sender { - [[frmAboutWindow defaultController] showWithSender:sender]; + // show the About window, and also make the IME app itself the focus + [(AppDelegate *)[NSApp delegate] showAbout]; + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } - (void)toggleChineseConverter:(id)sender diff --git a/Source/frmAboutWindow.m b/Source/frmAboutWindow.m index e90a969bd..e2bdf392f 100644 --- a/Source/frmAboutWindow.m +++ b/Source/frmAboutWindow.m @@ -60,27 +60,26 @@ - (void) windowDidLoad { [super windowDidLoad]; - + [self.window standardWindowButton:NSWindowCloseButton].hidden = true; + [self.window standardWindowButton:NSWindowMiniaturizeButton].hidden = true; + [self.window standardWindowButton:NSWindowZoomButton].hidden = true; [self updateInfo]; - - // If you add more custom subviews to display additional information about - // your app, configure them here } - (void) updateInfo { - NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary]; + + NSString *installingVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey]; + NSString *versionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + NSDictionary* localizedInfoDictionary = [[NSBundle mainBundle] localizedInfoDictionary]; self.appNameLabel.stringValue = [localizedInfoDictionary objectForKey:@"CFBundleName"]; - self.appVersionLabel.stringValue = [infoDictionary objectForKey:@"CFBundleShortVersionString"]; + self.appVersionLabel.stringValue = [NSString stringWithFormat:@"%@ Build %@", versionString, installingVersion]; self.appCopyrightLabel.stringValue = [localizedInfoDictionary objectForKey:@"NSHumanReadableCopyright"]; self.appEULAContent.string = [localizedInfoDictionary objectForKey:@"CFEULAContent"]; } - (void) showWithSender:(id)sender { - // FIXME: updating the strings every time is a temporary workaround - [self updateInfo]; - [self.window orderFront:sender]; } @end -- Gitee From 2dc471522923a483269f714b5d98016d9ada08aa Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Fri, 7 Jan 2022 22:55:41 +0800 Subject: [PATCH 017/163] Change License to MIT-NTL. --- LICENSE-CHS.txt | 16 +++++++++------- LICENSE-CHT.txt | 16 +++++++++------- LICENSE.txt | 17 ++++++++++------- README.md | 11 ++++++----- Source/Base.lproj/frmAboutWindow.xib | 2 +- Source/Installer/en.lproj/InfoPlist.strings | 2 +- Source/Installer/en.lproj/License.rtf | 8 +++++--- .../Installer/zh-Hans.lproj/InfoPlist.strings | 3 +-- Source/Installer/zh-Hans.lproj/License.rtf | 18 +++++++++--------- .../Installer/zh-Hant.lproj/InfoPlist.strings | 3 +-- Source/Installer/zh-Hant.lproj/License.rtf | 19 ++++++++++--------- Source/en.lproj/InfoPlist.strings | 2 +- Source/en.lproj/MITLicense.txt | 10 +++++++--- Source/en.lproj/frmAboutWindow.strings | 4 ++-- Source/zh-Hans.lproj/InfoPlist.strings | 2 +- Source/zh-Hans.lproj/MITLicense.txt | 10 +++++++--- Source/zh-Hans.lproj/frmAboutWindow.strings | 4 ++-- Source/zh-Hant.lproj/InfoPlist.strings | 2 +- Source/zh-Hant.lproj/MITLicense.txt | 10 +++++++--- Source/zh-Hant.lproj/frmAboutWindow.strings | 4 ++-- 20 files changed, 92 insertions(+), 71 deletions(-) diff --git a/LICENSE-CHS.txt b/LICENSE-CHS.txt index b0fb91024..09248e092 100644 --- a/LICENSE-CHS.txt +++ b/LICENSE-CHS.txt @@ -1,11 +1,13 @@ -vChewing macOS: MIT License 麻理许可协议 +vChewing macOS: MIT-NTL License 麻理(去商标)授权合约 -小麦注音引擎著作权利所有 © 2011-2022 OpenVanilla 专案(Mengjuei Hsieh, Lukhnos Liu, Zonble Yang 等)。 -威注音词库由孙志贵维护,感谢 Hiraku Wang 在程式功能扩展方面的协力。 -除特殊标注之处以外,威注音输入法维护人孙志贵对该产品的程序部分不享有任何所有权。 +© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project. +小麦注音引擎研发:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang (Zonble), 等。 +威注音 macOS 程式研发协力:Hiraku Wang。\n威注音词库维护:孙志贵 (Shiki Suen)。 -软件的著作权利人依此麻理授权条款,将其对于软件的著作权利授权释出,只要使用者践履以下二项麻理授权条款叙明的义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用的权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面的应用,而散布程式之人、更可将上述权利传递予其后收受程式的后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款的义务性规定,则其对程式亦享有与前手运用范围相同的同一权利。 +软件之著作权利人依此麻理授权条款,将其对于软件之著作权利授权释出,只须使用者践履以下二项麻理授权条款叙明之义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用之权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面之应用,而散布程式之人、更可将上述权利传递予其后收受程式之后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款之义务性规定,则其对程式亦享有与前手运用范围相同之同一权利。 -散布此一软件程序者,须将本条款其上的「著作权声明」及以下的「免责声明」,内嵌于软件程序及其重制作品的实体之中。 +甲、散布此一软件程序者,须将本条款其上之「著作权声明」及以下之「免责声明」内嵌于软件程序及其重制作品之实体之中。 -因麻理软件程序的授权模式乃是无偿提供,是以在现行法律的架构下可以主张合理的免除担保责任。麻理软件的著作权人或任何的后续散布者,对于其所散布的麻理软件程序皆不负任何形式上实质上的担保责任,明示亦或隐喻、商业利用性亦或特定目的使用性,这些均不在保障之列。利用麻理软件程序的所有风险均由使用者自行担负。假如所使用的麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要的服务支出。麻理软件程序的著作权人不负任何形式上实质上的担保责任,无论任何一般的、特殊的、偶发的、因果关系式的损害,或是麻理软件程序的不适用性,均须由使用者自行负担。 +乙、敝授权合约不提供对「贡献者」之商品名称、商标、服务标志或产品名称之商标许可,除非用以满足履行上文所述义务之必要。 + +因麻理软件程序之授权模式乃是无偿提供,是以在现行法律之架构下可以主张合理之免除担保责任。麻理软件之著作权人或任何之后续散布者,对于其所散布之麻理软件程序皆不负任何形式上实质上之担保责任,明示亦或隐喻、商业利用性亦或特定目之使用性,这些均不在保障之列。利用麻理软件程序之所有风险均由使用者自行担负。假如所使用之麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要之服务支出。麻理软件程序之著作权人不负任何形式上实质上之担保责任,无论任何一般之、特殊之、偶发之、因果关系式之损害,或是麻理软件程序之不适用性,均须由使用者自行负担。 diff --git a/LICENSE-CHT.txt b/LICENSE-CHT.txt index b072d9df6..1a7f68662 100644 --- a/LICENSE-CHT.txt +++ b/LICENSE-CHT.txt @@ -1,11 +1,13 @@ -vChewing macOS: MIT License 麻理授權條款 +vChewing macOS: MIT-NTL License 麻理(去商標)授權合約 -小麥注音引擎著作權利所有 © 2011-2022 OpenVanilla 專案(Mengjuei Hsieh, Lukhnos Liu, Zonble Yang 等)。 -威注音詞庫由孫志貴維護,感謝 Hiraku Wang 在程式功能擴展方面的協力。 -除特殊標注之處以外,威注音輸入法維護人孫志貴對該產品的程序部分不享有任何所有權。 +© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project. +小麥注音引擎研發:Mengjuei Hsieh, Lukhnos Liu, Zonble Yang (Zonble), 等。 +威注音 macOS 程式研發協力:Hiraku Wang。\n威注音詞庫維護:孫志貴 (Shiki Suen)。 -軟體的著作權利人依此麻理授權條款,將其對於軟體的著作權利授權釋出,只要使用者踐履以下二項麻理授權條款敘明的義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用的權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面的應用,而散布程式之人、更可將上述權利傳遞予其後收受程式的後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款的義務性規定,則其對程式亦享有與前手運用範圍相同的同一權利。 +軟體之著作權利人依此麻理授權條款,將其對於軟體之著作權利授權釋出,只須使用者踐履以下二項麻理授權條款敘明之義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用之權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面之應用,而散布程式之人、更可將上述權利傳遞予其後收受程式之後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款之義務性規定,則其對程式亦享有與前手運用範圍相同之同一權利。 -散布此一軟體程式者,須將本條款其上的「著作權聲明」及以下的「免責聲明」,內嵌於軟體程式及其重製作品的實體之中。 +甲、散布此一軟體程式者,須將本條款其上之「著作權聲明」及以下之「免責聲明」內嵌於軟體程式及其重製作品之實體之中。 -因麻理軟體程式的授權模式乃是無償提供,是以在現行法律的架構下可以主張合理的免除擔保責任。麻理軟體的著作權人或任何的後續散布者,對於其所散布的麻理軟體程式皆不負任何形式上實質上的擔保責任,明示亦或隱喻、商業利用性亦或特定目的使用性,這些均不在保障之列。利用麻理軟體程式的所有風險均由使用者自行擔負。假如所使用的麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要的服務支出。麻理軟體程式的著作權人不負任何形式上實質上的擔保責任,無論任何一般的、特殊的、偶發的、因果關係式的損害,或是麻理軟體程式的不適用性,均須由使用者自行負擔。 +乙、敝授權合約不提供對「貢獻者」之商品名稱、商標、服務標誌或產品名稱之商標許可,除非用以滿足履行上文所述義務之必要。 + +因麻理軟體程式之授權模式乃是無償提供,是以在現行法律之架構下可以主張合理之免除擔保責任。麻理軟體之著作權人或任何之後續散布者,對於其所散布之麻理軟體程式皆不負任何形式上實質上之擔保責任,明示亦或隱喻、商業利用性亦或特定目之使用性,這些均不在保障之列。利用麻理軟體程式之所有風險均由使用者自行擔負。假如所使用之麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要之服務支出。麻理軟體程式之著作權人不負任何形式上實質上之擔保責任,無論任何一般之、特殊之、偶發之、因果關係式之損害,或是麻理軟體程式之不適用性,均須由使用者自行負擔。 diff --git a/LICENSE.txt b/LICENSE.txt index 126d611e0..284cf9ae3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,11 +1,14 @@ -vChewing macOS: MIT License +vChewing macOS: MIT-NTL License -McBopomofo Engine Copyright (c) 2011-2022 OpenVanilla Project (Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.) -vChewing Phrase Database Maintained by Shiki Suen; Special Programming Reinforcements by Hiraku Wang. -Unless specially commented, Shiki Suen, the maintainer of the vChewing project, does not own any rights of the programming parts of it. +© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project. +McBopomofo Engine by Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al. +vChewing macOS Development Reinforced by Hiraku Wang. +
vChewing Phrase Database Maintained by Shiki Suen. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED “AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 54434bc97..0fb47def4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ ## 建置流程 -系统需求:至少 macOS 10.12 Sierra。 +建置用系统需求:至少 macOS 10.15 Catalina & Xcode 12。// 原因:Swift 封包管理支持所需。 +編譯出的成品對應系統需求:至少 macOS 10.12 Sierra。 安装 Xcode 之后,请先配置 Xcode 允许其直接构建在专案所在的资料夹下的 build 资料夹内。步骤: ``` @@ -20,12 +21,12 @@ 要注意的是 macOS 可能会限制同一次 login session 能终结同一个输入法的执行进程的次数(安装程式透过 kill input method process 来让新版的输入法生效)。如果安装若干次后,发现程式修改的结果并没有出现、或甚至输入法已无法再选用,只需要登出目前的 macOS 系统帐号、再重新登入即可。 -补记: 该输入法是在 2021 年 11 月初「28ae7deb4092f067539cff600397292e66a5dd56」这一版小麦注音建置的基础上完成的。因为在清洗词库的时候清洗了全部的 git commit 历史,所以无法自动从小麦注音官方仓库上游继承任何改动,只能手动同步任何在此之后的程式修正。最近一次同步參照是「f7a24862c4e1733a2264b56e434d1a449325d769」。除此以外,还引入了 MJHsieh 制作(却尚未正式给小麦注音实装)的「临时记忆最近的部分选字词」的功能(该记忆有自己的忘却衰减曲线,且记忆的词汇会在每次重新开机时自动忘却。)。 +补记: 该输入法是在 2021 年 11 月初「28ae7deb4092f067539cff600397292e66a5dd56」这一版小麦注音建置的基础上完成的。因为在清洗词库的时候清洗了全部的 git commit 历史,所以无法自动从小麦注音官方仓库上游继承任何改动,只能手动同步任何在此之后的程式修正。最近一次同步參照是「ffbfe42cb1c17f2c626ca31bda604fb5c9ec56b3」。 ## 应用授权 -小麦注音引擎程式版权:© 2011-2021 OpenVanilla 专案团队(Mengjuei Hsieh 等人)。 +小麦注音引擎程式版权(MIT 授权):© 2011-2021 OpenVanilla 专案团队(Mengjuei Hsieh 等人)。 -威注音词库由孙志贵维护,亦以 MIT 授权释出。 +威注音词库由孙志贵维护,以 MIT-MTL License 授权释出。 -本专案采用 MIT License 释出,使用者可自由使用、散播本软体,惟散播时必须完整保留版权声明及软体授权([详全文 LICENSE.txt](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。 +本专案采用 MIT-MTL License 释出:使用者可自由使用、散播本软体,惟散播时必须完整保留版权声明及软体授权、且一旦经过修改便不可以再继续使用威注音的产品名称。 diff --git a/Source/Base.lproj/frmAboutWindow.xib b/Source/Base.lproj/frmAboutWindow.xib index 4940e6a57..46211cec9 100644 --- a/Source/Base.lproj/frmAboutWindow.xib +++ b/Source/Base.lproj/frmAboutWindow.xib @@ -113,7 +113,7 @@ vChewing Phrase Database Maintained by Shiki Suen. - + diff --git a/Source/Installer/en.lproj/InfoPlist.strings b/Source/Installer/en.lproj/InfoPlist.strings index 657c8a41f..6efd7f045 100644 --- a/Source/Installer/en.lproj/InfoPlist.strings +++ b/Source/Installer/en.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ /* Localized versions of Info.plist keys */ CFBundleName = "Install vChewing"; -NSHumanReadableCopyright = "Copyright © 2011-2022 OpenVanilla Project (Mengjuei Hsieh et al.)\nAll Rights Reserved."; +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; diff --git a/Source/Installer/en.lproj/License.rtf b/Source/Installer/en.lproj/License.rtf index f90f96645..937871e1b 100644 --- a/Source/Installer/en.lproj/License.rtf +++ b/Source/Installer/en.lproj/License.rtf @@ -12,9 +12,11 @@ McBopomofo Engine Copyright (c) 2011-2022 OpenVanilla Project (Mengjuei Hsieh, L vChewing Phrase Database Maintained by Shiki Suen; Special Programming Reinforcements by Hiraku Wang.\ Unless specially commented, Shiki Suen, the maintainer of the vChewing project, does not own any rights of the programming parts of it.\ \ -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'93Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ \ -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ +1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ \ -THE SOFTWARE IS PROVIDED \'93AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ +2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ } \ No newline at end of file diff --git a/Source/Installer/zh-Hans.lproj/InfoPlist.strings b/Source/Installer/zh-Hans.lproj/InfoPlist.strings index 3338b4099..4eeef85e2 100644 --- a/Source/Installer/zh-Hans.lproj/InfoPlist.strings +++ b/Source/Installer/zh-Hans.lproj/InfoPlist.strings @@ -1,5 +1,4 @@ /* Localized versions of Info.plist keys */ CFBundleName = "安装威注音"; -NSHumanReadableCopyright = "Copyright © 2011-2022 OpenVanilla Project (Mengjuei Hsieh et al.)\nAll Rights Reserved."; - +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; diff --git a/Source/Installer/zh-Hans.lproj/License.rtf b/Source/Installer/zh-Hans.lproj/License.rtf index 8bfd2c9c4..00df0415d 100644 --- a/Source/Installer/zh-Hans.lproj/License.rtf +++ b/Source/Installer/zh-Hans.lproj/License.rtf @@ -1,5 +1,5 @@ {\rtf1\ansi\ansicpg950\cocoartf2636 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 SFProText-Bold;\f1\fnil\fcharset0 SFProText-Regular;\f2\fnil\fcharset136 PingFangTC-Regular; +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 SFProText-Bold;\f1\fnil\fcharset0 SFProText-Regular;\f2\fnil\fcharset136 PingFangSC-Regular; } {\colortbl;\red255\green255\blue255;} {\*\expandedcolortbl;;} @@ -27,15 +27,13 @@ \f1 \ \ -\f2 \uc0\u36719 \'a5\'f3\'aa\'ba\'b5\'db\'a7\'40\u26435 \'a7\'51\'a4\'48\'a8\'cc\'a6\'b9\'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\'a1\'41\u23558 \'a8\'e4\u23545 \'a4\'5f\u36719 \'a5\'f3\'aa\'ba\'b5\'db\'a7\'40\u26435 \'a7\'51\'b1\'c2\u26435 \u37322 \'a5\'58\'a1\'41\'a5\'75\'ad\'6e\'a8\'cf\'a5\'ce\'aa\'cc\u36341 \'bc\'69\'a5\'48\'a4\'55\'a4\'47\u39033 \'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\u21465 \'a9\'fa\'aa\'ba\u20041 \u21153 \'a9\'ca\u35268 \'a9\'77\'a1\'41\'a8\'e4\'a7\'59\'a8\'c9\'a6\'b3\u23545 \'a6\'b9\u36719 \'a5\'f3\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ac\'db\u20851 \u35828 \'a9\'fa\'a4\'e5\u26723 \'a6\'db\'a5\'d1\'a4\'a3\'a8\'fc\'ad\'ad\'a8\'ee\'a6\'61\u36827 \'a6\'e6\'a7\'51\'a5\'ce\'aa\'ba\u26435 \'a7\'51\'a1\'41\'ad\'53\u22260 \'a5\'5d\'ac\'41\'a1\'75\'a8\'cf\'a5\'ce\'a1\'42\'ad\'ab\'a8\'ee\'a1\'42\'ad\'d7\'a7\'ef\'a1\'42\'a6\'58\'a6\'7d\'a1\'42\'a5\'58\'aa\'a9\'a1\'42\'b4\'b2\'a5\'ac\'a1\'42\'a6\'41\'b1\'c2\u26435 \'a1\'42\'a4\'ce\u36137 \'b0\'e2\'b5\'7b\'a6\'a1\'ad\'ab\'a8\'ee\'a7\'40\'ab\'7e\'a1\'76\'b5\'a5\u35832 \'a6\'68\'a4\'e8\'ad\'b1\'aa\'ba\u24212 \'a5\'ce\'a1\'41\'a6\'d3\'b4\'b2\'a5\'ac\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a1\'42\'a7\'f3\'a5\'69\u23558 \'a4\'57\'ad\'7a\u26435 \'a7\'51\u20256 \u36882 \'a4\'a9\'a8\'e4\'a6\'5a\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'aa\'ba\'a6\'5a\'a4\'e2\'a1\'41\'ad\'d5\'ad\'59\'a8\'e4\'a6\'5a\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a5\'e7\'aa\'41\'c1\'74\'a5\'48\'a4\'55\'a4\'47\u39033 \'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\'aa\'ba\u20041 \u21153 \'a9\'ca\u35268 \'a9\'77\'a1\'41\u21017 \'a8\'e4\u23545 \'b5\'7b\'a6\'a1\'a5\'e7\'a8\'c9\'a6\'b3\'c9\'4f\'ab\'65\'a4\'e2\u36816 \'a5\'ce\'ad\'53\u22260 \'ac\'db\'a6\'50\'aa\'ba\'a6\'50\'a4\'40\u26435 \'a7\'51\'a1\'43 -\f1 \ +\f2 \uc0\u36719 \'a5\'f3\'a4\'a7\'b5\'db\'a7\'40\u26435 \'a7\'51\'a4\'48\'a8\'cc\'a6\'b9\'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\'a1\'41\u23558 \'a8\'e4\u23545 \'a4\'5f\u36719 \'a5\'f3\'a4\'a7\'b5\'db\'a7\'40\u26435 \'a7\'51\'b1\'c2\u26435 \u37322 \'a5\'58\'a1\'41\'a5\'75\u39035 \'a8\'cf\'a5\'ce\'aa\'cc\u36341 \'bc\'69\'a5\'48\'a4\'55\'a4\'47\u39033 \'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\u21465 \'a9\'fa\'a4\'a7\u20041 \u21153 \'a9\'ca\u35268 \'a9\'77\'a1\'41\'a8\'e4\'a7\'59\'a8\'c9\'a6\'b3\u23545 \'a6\'b9\u36719 \'a5\'f3\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ac\'db\u20851 \u35828 \'a9\'fa\'a4\'e5\u26723 \'a6\'db\'a5\'d1\'a4\'a3\'a8\'fc\'ad\'ad\'a8\'ee\'a6\'61\u36827 \'a6\'e6\'a7\'51\'a5\'ce\'a4\'a7\u26435 \'a7\'51\'a1\'41\'ad\'53\u22260 \'a5\'5d\'ac\'41\'a1\'75\'a8\'cf\'a5\'ce\'a1\'42\'ad\'ab\'a8\'ee\'a1\'42\'ad\'d7\'a7\'ef\'a1\'42\'a6\'58\'a6\'7d\'a1\'42\'a5\'58\'aa\'a9\'a1\'42\'b4\'b2\'a5\'ac\'a1\'42\'a6\'41\'b1\'c2\u26435 \'a1\'42\'a4\'ce\u36137 \'b0\'e2\'b5\'7b\'a6\'a1\'ad\'ab\'a8\'ee\'a7\'40\'ab\'7e\'a1\'76\'b5\'a5\u35832 \'a6\'68\'a4\'e8\'ad\'b1\'a4\'a7\u24212 \'a5\'ce\'a1\'41\'a6\'d3\'b4\'b2\'a5\'ac\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a1\'42\'a7\'f3\'a5\'69\u23558 \'a4\'57\'ad\'7a\u26435 \'a7\'51\u20256 \u36882 \'a4\'a9\'a8\'e4\'a6\'5a\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a6\'5a\'a4\'e2\'a1\'41\'ad\'d5\'ad\'59\'a8\'e4\'a6\'5a\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a5\'e7\'aa\'41\'c1\'74\'a5\'48\'a4\'55\'a4\'47\u39033 \'b3\'c2\'b2\'7a\'b1\'c2\u26435 \u26465 \'b4\'da\'a4\'a7\u20041 \u21153 \'a9\'ca\u35268 \'a9\'77\'a1\'41\u21017 \'a8\'e4\u23545 \'b5\'7b\'a6\'a1\'a5\'e7\'a8\'c9\'a6\'b3\'c9\'4f\'ab\'65\'a4\'e2\u36816 \'a5\'ce\'ad\'53\u22260 \'ac\'db\'a6\'50\'a4\'a7\'a6\'50\'a4\'40\u26435 \'a7\'51\'a1\'43\ \ - -\f2 \'b4\'b2\'a5\'ac\'a6\'b9\'a4\'40\uc0\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'cc\'a1\'41\u39035 \u23558 \'a5\'bb\u26465 \'b4\'da\'a8\'e4\'a4\'57\'aa\'ba\'a1\'75\'b5\'db\'a7\'40\u26435 \u22768 \'a9\'fa\'a1\'76\'a4\'ce\'a5\'48\'a4\'55\'aa\'ba\'a1\'75\'a7\'4b\u36131 \u22768 \'a9\'fa\'a1\'76\'a1\'41\u20869 \'b4\'4f\'a4\'5f\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'ce\'a8\'e4\'ad\'ab\'a8\'ee\'a7\'40\'ab\'7e\'aa\'ba\u23454 \'ca\'5e\'a4\'a7\'a4\'a4\'a1\'43 -\f1 \ +\'a5\'d2\'a1\'42\'b4\'b2\'a5\'ac\'a6\'b9\'a4\'40\uc0\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'cc\'a1\'41\u39035 \u23558 \'a5\'bb\u26465 \'b4\'da\'a8\'e4\'a4\'57\'a4\'a7\'a1\'75\'b5\'db\'a7\'40\u26435 \u22768 \'a9\'fa\'a1\'76\'a4\'ce\'a5\'48\'a4\'55\'a4\'a7\'a1\'75\'a7\'4b\u36131 \u22768 \'a9\'fa\'a1\'76\u20869 \'b4\'4f\'a4\'5f\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'ce\'a8\'e4\'ad\'ab\'a8\'ee\'a7\'40\'ab\'7e\'a4\'a7\u23454 \'ca\'5e\'a4\'a7\'a4\'a4\'a1\'43\ \ - -\f2 \'a6\'5d\'b3\'c2\'b2\'7a\uc0\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'ba\'b1\'c2\u26435 \'bc\'d2\'a6\'a1\'a4\'44\'ac\'4f\u26080 \u20607 \'b4\'a3\'a8\'d1\'a1\'41\'ac\'4f\'a5\'48\'a6\'62\u29616 \'a6\'e6\'aa\'6b\'ab\'df\'aa\'ba\'ac\'5b\'cc\'db\'a4\'55\'a5\'69\'a5\'48\'a5\'44\u24352 \'a6\'58\'b2\'7a\'aa\'ba\'a7\'4b\'b0\'a3\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'43\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'aa\'ba\'b5\'db\'a7\'40\u26435 \'a4\'48\'a9\'ce\'a5\'f4\'a6\'f3\'aa\'ba\'a6\'5a\u32493 \'b4\'b2\'a5\'ac\'aa\'cc\'a1\'41\u23545 \'a4\'5f\'a8\'e4\'a9\'d2\'b4\'b2\'a5\'ac\'aa\'ba\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'ac\'d2\'a4\'a3\u36127 \'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\u23454 \u36136 \'a4\'57\'aa\'ba\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'41\'a9\'fa\'a5\'dc\'a5\'e7\'a9\'ce\u38544 \'b3\'eb\'a1\'42\'b0\'d3\u19994 \'a7\'51\'a5\'ce\'a9\'ca\'a5\'e7\'a9\'ce\'af\'53\'a9\'77\'a5\'d8\'aa\'ba\'a8\'cf\'a5\'ce\'a9\'ca\'a1\'41\u36825 \'a8\'c7\'a7\'a1\'a4\'a3\'a6\'62\'ab\'4f\'bb\'d9\'a4\'a7\'a6\'43\'a1\'43\'a7\'51\'a5\'ce\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'ba\'a9\'d2\'a6\'b3\u39118 \u38505 \'a7\'a1\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\u25285 \u36127 \'a1\'43\'b0\'b2\'a6\'70\'a9\'d2\'a8\'cf\'a5\'ce\'aa\'ba\'b3\'c2\'b2\'7a\'b5\'7b\'a7\'c7\u21457 \'a5\'cd\'af\'ca\'b3\'b4\'a9\'ca\u38382 \u39064 \'a1\'41\'a8\'cf\'a5\'ce\'aa\'cc\'bb\'dd\'a6\'db\'a6\'e6\u25285 \u36127 \'ad\'d7\'a5\'bf\'a1\'42\'a7\'ef\'a5\'bf\'a4\'ce\'a5\'b2\'ad\'6e\'aa\'ba\'aa\'41\u21153 \'a4\'e4\'a5\'58\'a1\'43\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'ba\'b5\'db\'a7\'40\u26435 \'a4\'48\'a4\'a3\u36127 \'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\u23454 \u36136 \'a4\'57\'aa\'ba\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'41\u26080 \u35770 \'a5\'f4\'a6\'f3\'a4\'40\'af\'eb\'aa\'ba\'a1\'42\'af\'53\'ae\'ed\'aa\'ba\'a1\'42\'b0\'b8\u21457 \'aa\'ba\'a1\'42\'a6\'5d\'aa\'47\u20851 \'a8\'74\'a6\'a1\'aa\'ba\u25439 \'ae\'60\'a1\'41\'a9\'ce\'ac\'4f\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'aa\'ba\'a4\'a3\'d3\'ec\'a5\'ce\'a9\'ca\'a1\'41\'a7\'a1\u39035 \'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\u36127 \u25285 \'a1\'43 +\'a4\'41\'a1\'42\'b1\'cd\'b1\'c2\uc0\u26435 \'a6\'58\u32422 \'a4\'a3\'b4\'a3\'a8\'d1\u23545 \'a1\'75\u36129 \u29486 \'aa\'cc\'a1\'76\'a4\'a7\'b0\'d3\'ab\'7e\'a6\'57\u31216 \'a1\'42\'b0\'d3\u26631 \'a1\'42\'aa\'41\u21153 \u26631 \'a7\'d3\'a9\'ce\u20135 \'ab\'7e\'a6\'57\u31216 \'a4\'a7\'b0\'d3\u26631 \u35768 \'a5\'69\'a1\'41\'b0\'a3\'ab\'44\'a5\'ce\'a5\'48\u28385 \'a8\'ac\'bc\'69\'a6\'e6\'a4\'57\'a4\'e5\'a9\'d2\'ad\'7a\u20041 \u21153 \'a4\'a7\'a5\'b2\'ad\'6e\'a1\'43\ +\ +\'a6\'5d\'b3\'c2\'b2\'7a\uc0\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'a7\'b1\'c2\u26435 \'bc\'d2\'a6\'a1\'a4\'44\'ac\'4f\u26080 \u20607 \'b4\'a3\'a8\'d1\'a1\'41\'ac\'4f\'a5\'48\'a6\'62\u29616 \'a6\'e6\'aa\'6b\'ab\'df\'a4\'a7\'ac\'5b\'cc\'db\'a4\'55\'a5\'69\'a5\'48\'a5\'44\u24352 \'a6\'58\'b2\'7a\'a4\'a7\'a7\'4b\'b0\'a3\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'43\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'a4\'a7\'b5\'db\'a7\'40\u26435 \'a4\'48\'a9\'ce\'a5\'f4\'a6\'f3\'a4\'a7\'a6\'5a\u32493 \'b4\'b2\'a5\'ac\'aa\'cc\'a1\'41\u23545 \'a4\'5f\'a8\'e4\'a9\'d2\'b4\'b2\'a5\'ac\'a4\'a7\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'ac\'d2\'a4\'a3\u36127 \'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\u23454 \u36136 \'a4\'57\'a4\'a7\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'41\'a9\'fa\'a5\'dc\'a5\'e7\'a9\'ce\u38544 \'b3\'eb\'a1\'42\'b0\'d3\u19994 \'a7\'51\'a5\'ce\'a9\'ca\'a5\'e7\'a9\'ce\'af\'53\'a9\'77\'a5\'d8\'a4\'a7\'a8\'cf\'a5\'ce\'a9\'ca\'a1\'41\u36825 \'a8\'c7\'a7\'a1\'a4\'a3\'a6\'62\'ab\'4f\'bb\'d9\'a4\'a7\'a6\'43\'a1\'43\'a7\'51\'a5\'ce\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'a7\'a9\'d2\'a6\'b3\u39118 \u38505 \'a7\'a1\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\u25285 \u36127 \'a1\'43\'b0\'b2\'a6\'70\'a9\'d2\'a8\'cf\'a5\'ce\'a4\'a7\'b3\'c2\'b2\'7a\'b5\'7b\'a7\'c7\u21457 \'a5\'cd\'af\'ca\'b3\'b4\'a9\'ca\u38382 \u39064 \'a1\'41\'a8\'cf\'a5\'ce\'aa\'cc\'bb\'dd\'a6\'db\'a6\'e6\u25285 \u36127 \'ad\'d7\'a5\'bf\'a1\'42\'a7\'ef\'a5\'bf\'a4\'ce\'a5\'b2\'ad\'6e\'a4\'a7\'aa\'41\u21153 \'a4\'e4\'a5\'58\'a1\'43\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'a7\'b5\'db\'a7\'40\u26435 \'a4\'48\'a4\'a3\u36127 \'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\u23454 \u36136 \'a4\'57\'a4\'a7\u25285 \'ab\'4f\u36131 \'a5\'f4\'a1\'41\u26080 \u35770 \'a5\'f4\'a6\'f3\'a4\'40\'af\'eb\'a4\'a7\'a1\'42\'af\'53\'ae\'ed\'a4\'a7\'a1\'42\'b0\'b8\u21457 \'a4\'a7\'a1\'42\'a6\'5d\'aa\'47\u20851 \'a8\'74\'a6\'a1\'a4\'a7\u25439 \'ae\'60\'a1\'41\'a9\'ce\'ac\'4f\'b3\'c2\'b2\'7a\u36719 \'a5\'f3\'b5\'7b\'a7\'c7\'a4\'a7\'a4\'a3\'d3\'ec\'a5\'ce\'a9\'ca\'a1\'41\'a7\'a1\u39035 \'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\u36127 \u25285 \'a1\'43 \f1 \ \ McBopomofo Engine Copyright (c) 2011-2022 OpenVanilla Project (Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.)\ @@ -44,7 +42,9 @@ Unless specially commented, Shiki Suen, the maintainer of the vChewing project, \ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'93Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ \ -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ +1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ +\ +2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above.\ \ THE SOFTWARE IS PROVIDED \'93AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ } \ No newline at end of file diff --git a/Source/Installer/zh-Hant.lproj/InfoPlist.strings b/Source/Installer/zh-Hant.lproj/InfoPlist.strings index bdfa843b7..abd8551f5 100644 --- a/Source/Installer/zh-Hant.lproj/InfoPlist.strings +++ b/Source/Installer/zh-Hant.lproj/InfoPlist.strings @@ -1,5 +1,4 @@ /* Localized versions of Info.plist keys */ CFBundleName = "安裝威注音"; -NSHumanReadableCopyright = "Copyright © 2011-2022 OpenVanilla Project (Mengjuei Hsieh et al.)\nAll Rights Reserved."; - +NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; diff --git a/Source/Installer/zh-Hant.lproj/License.rtf b/Source/Installer/zh-Hant.lproj/License.rtf index 988f9c6b8..abd07f50a 100644 --- a/Source/Installer/zh-Hant.lproj/License.rtf +++ b/Source/Installer/zh-Hant.lproj/License.rtf @@ -27,24 +27,25 @@ \f1 \ \ -\f2 \'b3\'6e\'c5\'e9\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'a4\'48\'a8\'cc\'a6\'b9\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'a1\'41\'b1\'4e\'a8\'e4\'b9\'ef\'a9\'f3\'b3\'6e\'c5\'e9\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'b1\'c2\'c5\'76\'c4\'c0\'a5\'58\'a1\'41\'a5\'75\'ad\'6e\'a8\'cf\'a5\'ce\'aa\'cc\'bd\'ee\'bc\'69\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'b1\'d4\'a9\'fa\'aa\'ba\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'a8\'e4\'a7\'59\'a8\'c9\'a6\'b3\'b9\'ef\'a6\'b9\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ac\'db\'c3\'f6\'bb\'a1\'a9\'fa\'a4\'e5\'c0\'c9\'a6\'db\'a5\'d1\'a4\'a3\'a8\'fc\'ad\'ad\'a8\'ee\'a6\'61\'b6\'69\'a6\'e6\'a7\'51\'a5\'ce\'aa\'ba\'c5\'76\'a7\'51\'a1\'41\'bd\'64\'b3\'f2\'a5\'5d\'ac\'41\'a1\'75\'a8\'cf\'a5\'ce\'a1\'42\'ad\'ab\'bb\'73\'a1\'42\'ad\'d7\'a7\'ef\'a1\'42\'a6\'58\'a8\'d6\'a1\'42\'a5\'58\'aa\'a9\'a1\'42\'b4\'b2\'a5\'ac\'a1\'42\'a6\'41\'b1\'c2\'c5\'76\'a1\'42\'a4\'ce\'b3\'63\'b0\'e2\'b5\'7b\'a6\'a1\'ad\'ab\'bb\'73\'a7\'40\'ab\'7e\'a1\'76\'b5\'a5\'bd\'d1\'a6\'68\'a4\'e8\'ad\'b1\'aa\'ba\'c0\'b3\'a5\'ce\'a1\'41\'a6\'d3\'b4\'b2\'a5\'ac\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a1\'42\'a7\'f3\'a5\'69\'b1\'4e\'a4\'57\'ad\'7a\'c5\'76\'a7\'51\'b6\'c7\'bb\'bc\'a4\'a9\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'aa\'ba\'ab\'e1\'a4\'e2\'a1\'41\'ad\'d5\'ad\'59\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a5\'e7\'aa\'41\'c1\'74\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'aa\'ba\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'ab\'68\'a8\'e4\'b9\'ef\'b5\'7b\'a6\'a1\'a5\'e7\'a8\'c9\'a6\'b3\'bb\'50\'ab\'65\'a4\'e2\'b9\'42\'a5\'ce\'bd\'64\'b3\'f2\'ac\'db\'a6\'50\'aa\'ba\'a6\'50\'a4\'40\'c5\'76\'a7\'51\'a1\'43 -\f1 \ +\f2 \'b3\'6e\'c5\'e9\'a4\'a7\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'a4\'48\'a8\'cc\'a6\'b9\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'a1\'41\'b1\'4e\'a8\'e4\'b9\'ef\'a9\'f3\'b3\'6e\'c5\'e9\'a4\'a7\'b5\'db\'a7\'40\'c5\'76\'a7\'51\'b1\'c2\'c5\'76\'c4\'c0\'a5\'58\'a1\'41\'a5\'75\'b6\'b7\'a8\'cf\'a5\'ce\'aa\'cc\'bd\'ee\'bc\'69\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'b1\'d4\'a9\'fa\'a4\'a7\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'a8\'e4\'a7\'59\'a8\'c9\'a6\'b3\'b9\'ef\'a6\'b9\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ac\'db\'c3\'f6\'bb\'a1\'a9\'fa\'a4\'e5\'c0\'c9\'a6\'db\'a5\'d1\'a4\'a3\'a8\'fc\'ad\'ad\'a8\'ee\'a6\'61\'b6\'69\'a6\'e6\'a7\'51\'a5\'ce\'a4\'a7\'c5\'76\'a7\'51\'a1\'41\'bd\'64\'b3\'f2\'a5\'5d\'ac\'41\'a1\'75\'a8\'cf\'a5\'ce\'a1\'42\'ad\'ab\'bb\'73\'a1\'42\'ad\'d7\'a7\'ef\'a1\'42\'a6\'58\'a8\'d6\'a1\'42\'a5\'58\'aa\'a9\'a1\'42\'b4\'b2\'a5\'ac\'a1\'42\'a6\'41\'b1\'c2\'c5\'76\'a1\'42\'a4\'ce\'b3\'63\'b0\'e2\'b5\'7b\'a6\'a1\'ad\'ab\'bb\'73\'a7\'40\'ab\'7e\'a1\'76\'b5\'a5\'bd\'d1\'a6\'68\'a4\'e8\'ad\'b1\'a4\'a7\'c0\'b3\'a5\'ce\'a1\'41\'a6\'d3\'b4\'b2\'a5\'ac\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a1\'42\'a7\'f3\'a5\'69\'b1\'4e\'a4\'57\'ad\'7a\'c5\'76\'a7\'51\'b6\'c7\'bb\'bc\'a4\'a9\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'ab\'e1\'a4\'e2\'a1\'41\'ad\'d5\'ad\'59\'a8\'e4\'ab\'e1\'a6\'ac\'a8\'fc\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'48\'a5\'e7\'aa\'41\'c1\'74\'a5\'48\'a4\'55\'a4\'47\'b6\'b5\'b3\'c2\'b2\'7a\'b1\'c2\'c5\'76\'b1\'f8\'b4\'da\'a4\'a7\'b8\'71\'b0\'c8\'a9\'ca\'b3\'57\'a9\'77\'a1\'41\'ab\'68\'a8\'e4\'b9\'ef\'b5\'7b\'a6\'a1\'a5\'e7\'a8\'c9\'a6\'b3\'bb\'50\'ab\'65\'a4\'e2\'b9\'42\'a5\'ce\'bd\'64\'b3\'f2\'ac\'db\'a6\'50\'a4\'a7\'a6\'50\'a4\'40\'c5\'76\'a7\'51\'a1\'43\ \ - -\f2 \'b4\'b2\'a5\'ac\'a6\'b9\'a4\'40\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'cc\'a1\'41\'b6\'b7\'b1\'4e\'a5\'bb\'b1\'f8\'b4\'da\'a8\'e4\'a4\'57\'aa\'ba\'a1\'75\'b5\'db\'a7\'40\'c5\'76\'c1\'6e\'a9\'fa\'a1\'76\'a4\'ce\'a5\'48\'a4\'55\'aa\'ba\'a1\'75\'a7\'4b\'b3\'64\'c1\'6e\'a9\'fa\'a1\'76\'a1\'41\'a4\'ba\'b4\'4f\'a9\'f3\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ad\'ab\'bb\'73\'a7\'40\'ab\'7e\'aa\'ba\'b9\'ea\'c5\'e9\'a4\'a7\'a4\'a4\'a1\'43 -\f1 \ +\'a5\'d2\'a1\'42\'b4\'b2\'a5\'ac\'a6\'b9\'a4\'40\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'cc\'a1\'41\'b6\'b7\'b1\'4e\'a5\'bb\'b1\'f8\'b4\'da\'a8\'e4\'a4\'57\'a4\'a7\'a1\'75\'b5\'db\'a7\'40\'c5\'76\'c1\'6e\'a9\'fa\'a1\'76\'a4\'ce\'a5\'48\'a4\'55\'a4\'a7\'a1\'75\'a7\'4b\'b3\'64\'c1\'6e\'a9\'fa\'a1\'76\'a4\'ba\'b4\'4f\'a9\'f3\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'ce\'a8\'e4\'ad\'ab\'bb\'73\'a7\'40\'ab\'7e\'a4\'a7\'b9\'ea\'c5\'e9\'a4\'a7\'a4\'a4\'a1\'43\ \ - -\f2 \'a6\'5d\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'b1\'c2\'c5\'76\'bc\'d2\'a6\'a1\'a4\'44\'ac\'4f\'b5\'4c\'c0\'76\'b4\'a3\'a8\'d1\'a1\'41\'ac\'4f\'a5\'48\'a6\'62\'b2\'7b\'a6\'e6\'aa\'6b\'ab\'df\'aa\'ba\'ac\'5b\'ba\'63\'a4\'55\'a5\'69\'a5\'48\'a5\'44\'b1\'69\'a6\'58\'b2\'7a\'aa\'ba\'a7\'4b\'b0\'a3\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'43\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a9\'ce\'a5\'f4\'a6\'f3\'aa\'ba\'ab\'e1\'c4\'f2\'b4\'b2\'a5\'ac\'aa\'cc\'a1\'41\'b9\'ef\'a9\'f3\'a8\'e4\'a9\'d2\'b4\'b2\'a5\'ac\'aa\'ba\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'ac\'d2\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'aa\'ba\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'a9\'fa\'a5\'dc\'a5\'e7\'a9\'ce\'c1\'f4\'b3\'eb\'a1\'42\'b0\'d3\'b7\'7e\'a7\'51\'a5\'ce\'a9\'ca\'a5\'e7\'a9\'ce\'af\'53\'a9\'77\'a5\'d8\'aa\'ba\'a8\'cf\'a5\'ce\'a9\'ca\'a1\'41\'b3\'6f\'a8\'c7\'a7\'a1\'a4\'a3\'a6\'62\'ab\'4f\'bb\'d9\'a4\'a7\'a6\'43\'a1\'43\'a7\'51\'a5\'ce\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'a9\'d2\'a6\'b3\'ad\'b7\'c0\'49\'a7\'a1\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'a1\'43\'b0\'b2\'a6\'70\'a9\'d2\'a8\'cf\'a5\'ce\'aa\'ba\'b3\'c2\'b2\'7a\'b5\'7b\'a6\'a1\'b5\'6f\'a5\'cd\'af\'ca\'b3\'b4\'a9\'ca\'b0\'dd\'c3\'44\'a1\'41\'a8\'cf\'a5\'ce\'aa\'cc\'bb\'dd\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'ad\'d7\'a5\'bf\'a1\'42\'a7\'ef\'a5\'bf\'a4\'ce\'a5\'b2\'ad\'6e\'aa\'ba\'aa\'41\'b0\'c8\'a4\'e4\'a5\'58\'a1\'43\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'aa\'ba\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'b5\'4c\'bd\'d7\'a5\'f4\'a6\'f3\'a4\'40\'af\'eb\'aa\'ba\'a1\'42\'af\'53\'ae\'ed\'aa\'ba\'a1\'42\'b0\'b8\'b5\'6f\'aa\'ba\'a1\'42\'a6\'5d\'aa\'47\'c3\'f6\'ab\'59\'a6\'a1\'aa\'ba\'b7\'6c\'ae\'60\'a1\'41\'a9\'ce\'ac\'4f\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'aa\'ba\'a4\'a3\'be\'41\'a5\'ce\'a9\'ca\'a1\'41\'a7\'a1\'b6\'b7\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'ad\'74\'be\'e1\'a1\'43 -\f1 \ +\'a4\'41\'a1\'42\'b1\'cd\'b1\'c2\'c5\'76\'a6\'58\'ac\'f9\'a4\'a3\'b4\'a3\'a8\'d1\'b9\'ef\'a1\'75\'b0\'5e\'c4\'6d\'aa\'cc\'a1\'76\'a4\'a7\'b0\'d3\'ab\'7e\'a6\'57\'ba\'d9\'a1\'42\'b0\'d3\'bc\'d0\'a1\'42\'aa\'41\'b0\'c8\'bc\'d0\'bb\'78\'a9\'ce\'b2\'a3\'ab\'7e\'a6\'57\'ba\'d9\'a4\'a7\'b0\'d3\'bc\'d0\'b3\'5c\'a5\'69\'a1\'41\'b0\'a3\'ab\'44\'a5\'ce\'a5\'48\'ba\'a1\'a8\'ac\'bc\'69\'a6\'e6\'a4\'57\'a4\'e5\'a9\'d2\'ad\'7a\'b8\'71\'b0\'c8\'a4\'a7\'a5\'b2\'ad\'6e\'a1\'43\ \ +\'a6\'5d\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'a7\'b1\'c2\'c5\'76\'bc\'d2\'a6\'a1\'a4\'44\'ac\'4f\'b5\'4c\'c0\'76\'b4\'a3\'a8\'d1\'a1\'41\'ac\'4f\'a5\'48\'a6\'62\'b2\'7b\'a6\'e6\'aa\'6b\'ab\'df\'a4\'a7\'ac\'5b\'ba\'63\'a4\'55\'a5\'69\'a5\'48\'a5\'44\'b1\'69\'a6\'58\'b2\'7a\'a4\'a7\'a7\'4b\'b0\'a3\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'43\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'a4\'a7\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a9\'ce\'a5\'f4\'a6\'f3\'a4\'a7\'ab\'e1\'c4\'f2\'b4\'b2\'a5\'ac\'aa\'cc\'a1\'41\'b9\'ef\'a9\'f3\'a8\'e4\'a9\'d2\'b4\'b2\'a5\'ac\'a4\'a7\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'ac\'d2\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'a4\'a7\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'a9\'fa\'a5\'dc\'a5\'e7\'a9\'ce\'c1\'f4\'b3\'eb\'a1\'42\'b0\'d3\'b7\'7e\'a7\'51\'a5\'ce\'a9\'ca\'a5\'e7\'a9\'ce\'af\'53\'a9\'77\'a5\'d8\'a4\'a7\'a8\'cf\'a5\'ce\'a9\'ca\'a1\'41\'b3\'6f\'a8\'c7\'a7\'a1\'a4\'a3\'a6\'62\'ab\'4f\'bb\'d9\'a4\'a7\'a6\'43\'a1\'43\'a7\'51\'a5\'ce\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'a7\'a9\'d2\'a6\'b3\'ad\'b7\'c0\'49\'a7\'a1\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'a1\'43\'b0\'b2\'a6\'70\'a9\'d2\'a8\'cf\'a5\'ce\'a4\'a7\'b3\'c2\'b2\'7a\'b5\'7b\'a6\'a1\'b5\'6f\'a5\'cd\'af\'ca\'b3\'b4\'a9\'ca\'b0\'dd\'c3\'44\'a1\'41\'a8\'cf\'a5\'ce\'aa\'cc\'bb\'dd\'a6\'db\'a6\'e6\'be\'e1\'ad\'74\'ad\'d7\'a5\'bf\'a1\'42\'a7\'ef\'a5\'bf\'a4\'ce\'a5\'b2\'ad\'6e\'a4\'a7\'aa\'41\'b0\'c8\'a4\'e4\'a5\'58\'a1\'43\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'a7\'b5\'db\'a7\'40\'c5\'76\'a4\'48\'a4\'a3\'ad\'74\'a5\'f4\'a6\'f3\'a7\'ce\'a6\'a1\'a4\'57\'b9\'ea\'bd\'e8\'a4\'57\'a4\'a7\'be\'e1\'ab\'4f\'b3\'64\'a5\'f4\'a1\'41\'b5\'4c\'bd\'d7\'a5\'f4\'a6\'f3\'a4\'40\'af\'eb\'a4\'a7\'a1\'42\'af\'53\'ae\'ed\'a4\'a7\'a1\'42\'b0\'b8\'b5\'6f\'a4\'a7\'a1\'42\'a6\'5d\'aa\'47\'c3\'f6\'ab\'59\'a6\'a1\'a4\'a7\'b7\'6c\'ae\'60\'a1\'41\'a9\'ce\'ac\'4f\'b3\'c2\'b2\'7a\'b3\'6e\'c5\'e9\'b5\'7b\'a6\'a1\'a4\'a7\'a4\'a3\'be\'41\'a5\'ce\'a9\'ca\'a1\'41\'a7\'a1\'b6\'b7\'a5\'d1\'a8\'cf\'a5\'ce\'aa\'cc\'a6\'db\'a6\'e6\'ad\'74\'be\'e1\'a1\'43\ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 + +\f1 \cf0 \ McBopomofo Engine Copyright (c) 2011-2022 OpenVanilla Project (Mengjuei Hsieh, Lukhnos Liu, Zonble Yang, et al.)\ vChewing Phrase Database Maintained by Shiki Suen; Special Programming Reinforcements by Hiraku Wang.\ Unless specially commented, Shiki Suen, the maintainer of the vChewing project, does not own any rights of the programming parts of it.\ \ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \'93Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\ \ -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ +1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\ +\ +2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above.\ \ THE SOFTWARE IS PROVIDED \'93AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\ } \ No newline at end of file diff --git a/Source/en.lproj/InfoPlist.strings b/Source/en.lproj/InfoPlist.strings index 09f06fe87..66cd73c64 100644 --- a/Source/en.lproj/InfoPlist.strings +++ b/Source/en.lproj/InfoPlist.strings @@ -3,4 +3,4 @@ CFBundleDisplayName = "vChewing"; NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; "org.openvanilla.inputmethod.vChewing.Bopomofo" = "vChewing-CHT"; "org.openvanilla.inputmethod.vChewing.SimpBopomofo" = "vChewing-CHS"; -CFEULAContent = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."; +CFEULAContent = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\n1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\n2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"; diff --git a/Source/en.lproj/MITLicense.txt b/Source/en.lproj/MITLicense.txt index 3488b7d02..ebf38b73d 100644 --- a/Source/en.lproj/MITLicense.txt +++ b/Source/en.lproj/MITLicense.txt @@ -1,5 +1,9 @@ -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +MIT NO-TRADEMARK LICENSE: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED “AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Source/en.lproj/frmAboutWindow.strings b/Source/en.lproj/frmAboutWindow.strings index 432fbaecf..c5e24c05b 100644 --- a/Source/en.lproj/frmAboutWindow.strings +++ b/Source/en.lproj/frmAboutWindow.strings @@ -1,6 +1,6 @@ -/* Class = "NSTextFieldCell"; title = "MIT License:"; ObjectID = "lblMITLicense"; */ -"lblMITLicense.title" = "MIT License:"; +/* Class = "NSTextFieldCell"; title = "MIT-NTL License:"; ObjectID = "lblMITLicense"; */ +"lblMITLicense.title" = "MIT-NTL License:"; /* Class = "NSTextFieldCell"; title = "version"; ObjectID = "lblVersionString"; */ // "lblVersionString.title" = "version"; diff --git a/Source/zh-Hans.lproj/InfoPlist.strings b/Source/zh-Hans.lproj/InfoPlist.strings index 4de42942f..f99671825 100644 --- a/Source/zh-Hans.lproj/InfoPlist.strings +++ b/Source/zh-Hans.lproj/InfoPlist.strings @@ -3,4 +3,4 @@ CFBundleDisplayName = "威注音"; NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; "org.openvanilla.inputmethod.vChewing.Bopomofo" = "威註音"; "org.openvanilla.inputmethod.vChewing.SimpBopomofo" = "威注音"; -CFEULAContent = "软件的著作权利人依此麻理授权条款,将其对于软件的著作权利授权释出,只要使用者践履以下二项麻理授权条款叙明的义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用的权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面的应用,而散布程式之人、更可将上述权利传递予其后收受程式的后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款的义务性规定,则其对程式亦享有与前手运用范围相同的同一权利。\n\n散布此一软件程序者,须将本条款其上的「著作权声明」及以下的「免责声明」,内嵌于软件程序及其重制作品的实体之中。\n\n因麻理软件程序的授权模式乃是无偿提供,是以在现行法律的架构下可以主张合理的免除担保责任。麻理软件的著作权人或任何的后续散布者,对于其所散布的麻理软件程序皆不负任何形式上实质上的担保责任,明示亦或隐喻、商业利用性亦或特定目的使用性,这些均不在保障之列。利用麻理软件程序的所有风险均由使用者自行担负。假如所使用的麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要的服务支出。麻理软件程序的著作权人不负任何形式上实质上的担保责任,无论任何一般的、特殊的、偶发的、因果关系式的损害,或是麻理软件程序的不适用性,均须由使用者自行负担。"; +CFEULAContent = "软件之著作权利人依此麻理授权条款,将其对于软件之著作权利授权释出,只须使用者践履以下二项麻理授权条款叙明之义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用之权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面之应用,而散布程式之人、更可将上述权利传递予其后收受程式之后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款之义务性规定,则其对程式亦享有与前手运用范围相同之同一权利。\n\n甲、散布此一软件程序者,须将本条款其上之「著作权声明」及以下之「免责声明」内嵌于软件程序及其重制作品之实体之中。\n\n乙、敝授权合约不提供对「贡献者」之商品名称、商标、服务标志或产品名称之商标许可,除非用以满足履行上文所述义务之必要。\n\n因麻理软件程序之授权模式乃是无偿提供,是以在现行法律之架构下可以主张合理之免除担保责任。麻理软件之著作权人或任何之后续散布者,对于其所散布之麻理软件程序皆不负任何形式上实质上之担保责任,明示亦或隐喻、商业利用性亦或特定目之使用性,这些均不在保障之列。利用麻理软件程序之所有风险均由使用者自行担负。假如所使用之麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要之服务支出。麻理软件程序之著作权人不负任何形式上实质上之担保责任,无论任何一般之、特殊之、偶发之、因果关系式之损害,或是麻理软件程序之不适用性,均须由使用者自行负担。\n"; diff --git a/Source/zh-Hans.lproj/MITLicense.txt b/Source/zh-Hans.lproj/MITLicense.txt index 8823b7808..1db1ce22a 100644 --- a/Source/zh-Hans.lproj/MITLicense.txt +++ b/Source/zh-Hans.lproj/MITLicense.txt @@ -1,5 +1,9 @@ -軟體的著作權利人依此麻理授權條款,將其對於軟體的著作權利授權釋出,只要使用者踐履以下二項麻理授權條款敘明的義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用的權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面的應用,而散布程式之人、更可將上述權利傳遞予其後收受程式的後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款的義務性規定,則其對程式亦享有與前手運用範圍相同的同一權利。 +麻理(去商标)授权合约 -散布此一軟體程式者,須將本條款其上的「著作權聲明」及以下的「免責聲明」,內嵌於軟體程式及其重製作品的實體之中。 +软件之著作权利人依此麻理授权条款,将其对于软件之著作权利授权释出,只须使用者践履以下二项麻理授权条款叙明之义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用之权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面之应用,而散布程式之人、更可将上述权利传递予其后收受程式之后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款之义务性规定,则其对程式亦享有与前手运用范围相同之同一权利。 -因麻理軟體程式的授權模式乃是無償提供,是以在現行法律的架構下可以主張合理的免除擔保責任。麻理軟體的著作權人或任何的後續散布者,對於其所散布的麻理軟體程式皆不負任何形式上實質上的擔保責任,明示亦或隱喻、商業利用性亦或特定目的使用性,這些均不在保障之列。利用麻理軟體程式的所有風險均由使用者自行擔負。假如所使用的麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要的服務支出。麻理軟體程式的著作權人不負任何形式上實質上的擔保責任,無論任何一般的、特殊的、偶發的、因果關係式的損害,或是麻理軟體程式的不適用性,均須由使用者自行負擔。 +甲、散布此一软件程序者,须将本条款其上之「著作权声明」及以下之「免责声明」内嵌于软件程序及其重制作品之实体之中。 + +乙、敝授权合约不提供对「贡献者」之商品名称、商标、服务标志或产品名称之商标许可,除非用以满足履行上文所述义务之必要。 + +因麻理软件程序之授权模式乃是无偿提供,是以在现行法律之架构下可以主张合理之免除担保责任。麻理软件之著作权人或任何之后续散布者,对于其所散布之麻理软件程序皆不负任何形式上实质上之担保责任,明示亦或隐喻、商业利用性亦或特定目之使用性,这些均不在保障之列。利用麻理软件程序之所有风险均由使用者自行担负。假如所使用之麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要之服务支出。麻理软件程序之著作权人不负任何形式上实质上之担保责任,无论任何一般之、特殊之、偶发之、因果关系式之损害,或是麻理软件程序之不适用性,均须由使用者自行负担。 diff --git a/Source/zh-Hans.lproj/frmAboutWindow.strings b/Source/zh-Hans.lproj/frmAboutWindow.strings index 017c4e33f..34b9c0fb7 100644 --- a/Source/zh-Hans.lproj/frmAboutWindow.strings +++ b/Source/zh-Hans.lproj/frmAboutWindow.strings @@ -1,6 +1,6 @@ -/* Class = "NSTextFieldCell"; title = "MIT License:"; ObjectID = "lblMITLicense"; */ -"lblMITLicense.title" = "麻理许可协议 (MIT License):"; +/* Class = "NSTextFieldCell"; title = "MIT-NTL License:"; ObjectID = "lblMITLicense"; */ +"lblMITLicense.title" = "麻理去商标授权合约 (MIT-NTL License):"; /* Class = "NSTextFieldCell"; title = "version"; ObjectID = "lblVersionString"; */ // "lblVersionString.title" = "版本"; diff --git a/Source/zh-Hant.lproj/InfoPlist.strings b/Source/zh-Hant.lproj/InfoPlist.strings index 770cbb8aa..589126e89 100644 --- a/Source/zh-Hant.lproj/InfoPlist.strings +++ b/Source/zh-Hant.lproj/InfoPlist.strings @@ -3,4 +3,4 @@ CFBundleDisplayName = "威注音"; NSHumanReadableCopyright = "© 2011-2022 OpenVanilla Project & © 2021-2022 vChewing Project."; "org.openvanilla.inputmethod.vChewing.Bopomofo" = "威註音"; "org.openvanilla.inputmethod.vChewing.SimpBopomofo" = "威注音"; -CFEULAContent = "軟體的著作權利人依此麻理授權條款,將其對於軟體的著作權利授權釋出,只要使用者踐履以下二項麻理授權條款敘明的義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用的權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面的應用,而散布程式之人、更可將上述權利傳遞予其後收受程式的後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款的義務性規定,則其對程式亦享有與前手運用範圍相同的同一權利。\n\n散布此一軟體程式者,須將本條款其上的「著作權聲明」及以下的「免責聲明」,內嵌於軟體程式及其重製作品的實體之中。\n\n因麻理軟體程式的授權模式乃是無償提供,是以在現行法律的架構下可以主張合理的免除擔保責任。麻理軟體的著作權人或任何的後續散布者,對於其所散布的麻理軟體程式皆不負任何形式上實質上的擔保責任,明示亦或隱喻、商業利用性亦或特定目的使用性,這些均不在保障之列。利用麻理軟體程式的所有風險均由使用者自行擔負。假如所使用的麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要的服務支出。麻理軟體程式的著作權人不負任何形式上實質上的擔保責任,無論任何一般的、特殊的、偶發的、因果關係式的損害,或是麻理軟體程式的不適用性,均須由使用者自行負擔。"; +CFEULAContent = "軟體之著作權利人依此麻理授權條款,將其對於軟體之著作權利授權釋出,只須使用者踐履以下二項麻理授權條款敘明之義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用之權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面之應用,而散布程式之人、更可將上述權利傳遞予其後收受程式之後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款之義務性規定,則其對程式亦享有與前手運用範圍相同之同一權利。\n\n甲、散布此一軟體程式者,須將本條款其上之「著作權聲明」及以下之「免責聲明」內嵌於軟體程式及其重製作品之實體之中。\n\n乙、敝授權合約不提供對「貢獻者」之商品名稱、商標、服務標誌或產品名稱之商標許可,除非用以滿足履行上文所述義務之必要。\n\n因麻理軟體程式之授權模式乃是無償提供,是以在現行法律之架構下可以主張合理之免除擔保責任。麻理軟體之著作權人或任何之後續散布者,對於其所散布之麻理軟體程式皆不負任何形式上實質上之擔保責任,明示亦或隱喻、商業利用性亦或特定目之使用性,這些均不在保障之列。利用麻理軟體程式之所有風險均由使用者自行擔負。假如所使用之麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要之服務支出。麻理軟體程式之著作權人不負任何形式上實質上之擔保責任,無論任何一般之、特殊之、偶發之、因果關係式之損害,或是麻理軟體程式之不適用性,均須由使用者自行負擔。\n"; diff --git a/Source/zh-Hant.lproj/MITLicense.txt b/Source/zh-Hant.lproj/MITLicense.txt index 403c88875..336b6cedc 100644 --- a/Source/zh-Hant.lproj/MITLicense.txt +++ b/Source/zh-Hant.lproj/MITLicense.txt @@ -1,5 +1,9 @@ -软件的著作权利人依此麻理授权条款,将其对于软件的著作权利授权释出,只要使用者践履以下二项麻理授权条款叙明的义务性规定,其即享有对此软件程式及其相关说明文档自由不受限制地进行利用的权利,范围包括「使用、重制、修改、合并、出版、散布、再授权、及贩售程式重制作品」等诸多方面的应用,而散布程式之人、更可将上述权利传递予其后收受程式的后手,倘若其后收受程式之人亦服膺以下二项麻理授权条款的义务性规定,则其对程式亦享有与前手运用范围相同的同一权利。 +麻理(去商標)授權合約 -散布此一软件程序者,须将本条款其上的「著作权声明」及以下的「免责声明」,内嵌于软件程序及其重制作品的实体之中。 +軟體之著作權利人依此麻理授權條款,將其對於軟體之著作權利授權釋出,只須使用者踐履以下二項麻理授權條款敘明之義務性規定,其即享有對此軟體程式及其相關說明文檔自由不受限制地進行利用之權利,範圍包括「使用、重製、修改、合併、出版、散布、再授權、及販售程式重製作品」等諸多方面之應用,而散布程式之人、更可將上述權利傳遞予其後收受程式之後手,倘若其後收受程式之人亦服膺以下二項麻理授權條款之義務性規定,則其對程式亦享有與前手運用範圍相同之同一權利。 -因麻理软件程序的授权模式乃是无偿提供,是以在现行法律的架构下可以主张合理的免除担保责任。麻理软件的著作权人或任何的后续散布者,对于其所散布的麻理软件程序皆不负任何形式上实质上的担保责任,明示亦或隐喻、商业利用性亦或特定目的使用性,这些均不在保障之列。利用麻理软件程序的所有风险均由使用者自行担负。假如所使用的麻理程序发生缺陷性问题,使用者需自行担负修正、改正及必要的服务支出。麻理软件程序的著作权人不负任何形式上实质上的担保责任,无论任何一般的、特殊的、偶发的、因果关系式的损害,或是麻理软件程序的不适用性,均须由使用者自行负担。 +甲、散布此一軟體程式者,須將本條款其上之「著作權聲明」及以下之「免責聲明」內嵌於軟體程式及其重製作品之實體之中。 + +乙、敝授權合約不提供對「貢獻者」之商品名稱、商標、服務標誌或產品名稱之商標許可,除非用以滿足履行上文所述義務之必要。 + +因麻理軟體程式之授權模式乃是無償提供,是以在現行法律之架構下可以主張合理之免除擔保責任。麻理軟體之著作權人或任何之後續散布者,對於其所散布之麻理軟體程式皆不負任何形式上實質上之擔保責任,明示亦或隱喻、商業利用性亦或特定目之使用性,這些均不在保障之列。利用麻理軟體程式之所有風險均由使用者自行擔負。假如所使用之麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要之服務支出。麻理軟體程式之著作權人不負任何形式上實質上之擔保責任,無論任何一般之、特殊之、偶發之、因果關係式之損害,或是麻理軟體程式之不適用性,均須由使用者自行負擔。 diff --git a/Source/zh-Hant.lproj/frmAboutWindow.strings b/Source/zh-Hant.lproj/frmAboutWindow.strings index 2d49020b6..b0ce427dd 100644 --- a/Source/zh-Hant.lproj/frmAboutWindow.strings +++ b/Source/zh-Hant.lproj/frmAboutWindow.strings @@ -1,6 +1,6 @@ -/* Class = "NSTextFieldCell"; title = "MIT License:"; ObjectID = "lblMITLicense"; */ -"lblMITLicense.title" = "麻理授權合約 (MIT License):"; +/* Class = "NSTextFieldCell"; title = "MIT-NTL License:"; ObjectID = "lblMITLicense"; */ +"lblMITLicense.title" = "麻理去商標授權合約 (MIT-NTL License):"; /* Class = "NSTextFieldCell"; title = "version"; ObjectID = "lblVersionString"; */ // "lblVersionString.title" = "版本"; -- Gitee From 5bd0d3873ebe7068b0682e246aa8c8f2cdc493ce Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sat, 8 Jan 2022 00:37:56 +0800 Subject: [PATCH 018/163] New Installer Interface. - Removing License.rtf due to its unnecessity. --- Source/Installer/AppDelegate.h | 4 + Source/Installer/AppDelegate.m | 30 +- Source/Installer/Base.lproj/MainMenu.xib | 302 +++++++++++++----- Source/Installer/Installer-Info.plist | 2 + Source/Installer/en.lproj/InfoPlist.strings | 3 +- Source/Installer/en.lproj/License.rtf | 22 -- Source/Installer/en.lproj/Localizable.strings | 20 +- Source/Installer/en.lproj/MainMenu.strings | 72 +++++ .../Installer/zh-Hans.lproj/InfoPlist.strings | 3 +- Source/Installer/zh-Hans.lproj/License.rtf | 50 --- .../zh-Hans.lproj/Localizable.strings | 24 +- .../Installer/zh-Hans.lproj/MainMenu.strings | 73 +++++ Source/Installer/zh-Hans.lproj/MainMenu.xib | 209 ------------ .../Installer/zh-Hant.lproj/InfoPlist.strings | 3 +- Source/Installer/zh-Hant.lproj/License.rtf | 51 --- .../zh-Hant.lproj/Localizable.strings | 22 +- .../Installer/zh-Hant.lproj/MainMenu.strings | 73 +++++ Source/Installer/zh-Hant.lproj/MainMenu.xib | 209 ------------ vChewing.xcodeproj/project.pbxproj | 29 +- 19 files changed, 478 insertions(+), 723 deletions(-) delete mode 100644 Source/Installer/en.lproj/License.rtf create mode 100644 Source/Installer/en.lproj/MainMenu.strings delete mode 100644 Source/Installer/zh-Hans.lproj/License.rtf create mode 100644 Source/Installer/zh-Hans.lproj/MainMenu.strings delete mode 100644 Source/Installer/zh-Hans.lproj/MainMenu.xib delete mode 100644 Source/Installer/zh-Hant.lproj/License.rtf create mode 100644 Source/Installer/zh-Hant.lproj/MainMenu.strings delete mode 100644 Source/Installer/zh-Hant.lproj/MainMenu.xib diff --git a/Source/Installer/AppDelegate.h b/Source/Installer/AppDelegate.h index 20a063315..d493a01d4 100644 --- a/Source/Installer/AppDelegate.h +++ b/Source/Installer/AppDelegate.h @@ -50,4 +50,8 @@ @property (unsafe_unretained) IBOutlet NSTextView *textView; @property (weak) IBOutlet NSWindow *progressSheet; @property (weak) IBOutlet NSProgressIndicator *progressIndicator; +@property (nonatomic) IBOutlet NSTextField *appNameLabel; +@property (nonatomic) IBOutlet NSTextField *appVersionLabel; +@property (nonatomic) IBOutlet NSTextField *appCopyrightLabel; +@property (nonatomic) IBOutlet NSTextView *appEULAContent; @end diff --git a/Source/Installer/AppDelegate.m b/Source/Installer/AppDelegate.m index 428e9fcf7..dd9441598 100644 --- a/Source/Installer/AppDelegate.m +++ b/Source/Installer/AppDelegate.m @@ -55,9 +55,14 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) { @synthesize textView = _textView; @synthesize progressSheet = _progressSheet; @synthesize progressIndicator = _progressIndicator; +@synthesize appNameLabel; +@synthesize appVersionLabel; +@synthesize appCopyrightLabel; +@synthesize appEULAContent; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + NSLog(@"vChewing: Application Launched."); _installingVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:(id)kCFBundleVersionKey]; NSString *versionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; @@ -68,14 +73,19 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) { [self.installButton setNextKeyView:self.cancelButton]; [[self window] setDefaultButtonCell:[self.installButton cell]]; - NSAttributedString *attrStr = [[NSAttributedString alloc] initWithRTF:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"License" ofType:@"rtf"]] documentAttributes:NULL]; - - NSMutableAttributedString *mutableAttrStr = [attrStr mutableCopy]; - [mutableAttrStr addAttribute:NSForegroundColorAttributeName value:[NSColor controlTextColor] range:NSMakeRange(0, [mutableAttrStr length])]; - [[self.textView textStorage] setAttributedString:mutableAttrStr]; - [self.textView setSelectedRange:NSMakeRange(0, 0)]; - - [[self window] setTitle:[NSString stringWithFormat:NSLocalizedString(@"%@ (for version %@, r%@)", nil), [[self window] title], versionString, _installingVersion]]; + + [self.window standardWindowButton:NSWindowCloseButton].hidden = true; + [self.window standardWindowButton:NSWindowMiniaturizeButton].hidden = true; + [self.window standardWindowButton:NSWindowZoomButton].hidden = true; + + // Update Info + NSDictionary* localizedInfoDictionary = [[NSBundle mainBundle] localizedInfoDictionary]; + + self.appNameLabel.stringValue = [localizedInfoDictionary objectForKey:@"CFBundleName"]; + // self.appVersionLabel.stringValue = [infoDictionary objectForKey:@"CFBundleShortVersionString"]; + self.appVersionLabel.stringValue = [NSString stringWithFormat:@"%@ Build %@", versionString, _installingVersion]; + self.appCopyrightLabel.stringValue = [localizedInfoDictionary objectForKey:@"NSHumanReadableCopyright"]; + self.appEULAContent.string = [localizedInfoDictionary objectForKey:@"CFEULAContent"]; if ([[NSFileManager defaultManager] fileExistsAtPath:[kTargetPartialPath stringByExpandingTildeInPath]]) { NSBundle *currentBundle = [NSBundle bundleWithPath:[kTargetPartialPath stringByExpandingTildeInPath]]; @@ -90,11 +100,11 @@ void RunAlertPanel(NSString *title, NSString *message, NSString *buttonTitle) { } if (_upgrading) { - [_installButton setTitle:NSLocalizedString(@"Agree and Upgrade", nil)]; + [_installButton setTitle:NSLocalizedString(@"Upgrade", nil)]; } [[self window] center]; - [[self window] orderFront:self]; + [[self window] orderFrontRegardless]; [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } diff --git a/Source/Installer/Base.lproj/MainMenu.xib b/Source/Installer/Base.lproj/MainMenu.xib index 072ea56f0..ff279a715 100644 --- a/Source/Installer/Base.lproj/MainMenu.xib +++ b/Source/Installer/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -18,7 +18,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -60,78 +60,168 @@ - - - - - - - - - - - - - - - - - - - - + +