/// <summary> /// Ctor: init immutable instance. /// </summary> public CedictAnnotation(int entryId, SearchScript script, int start, int length) { EntryId = entryId; Script = script; StartInQuery = start; LengthInQuery = length; }
/// <summary> /// Displays the received results, discarding existing data. /// </summary> /// <param name="lookupId">ID of lookup whose results are shown. If ID is smaller than last seen value, we don't show results.</param> /// <param name="entryProvider">The entry provider; ownership passed by caller to me.</param> /// <param name="results">Cedict lookup results to show.</param> /// <param name="script">Defines which script(s) to show.</param> /// <returns>True if results got shown; false if they're discarded because newer results are already on display.</returns> public bool SetResults(int lookupId, ICedictEntryProvider entryProvider, ReadOnlyCollection <CedictResult> results, SearchScript script) { #if DEBUG // Make us crash at bottom if first result "柏林" (comes up for "bolin") if (results.Count > 0) { CedictEntry entry = entryProvider.GetEntry(results[0].EntryId); if (entry.ChSimpl == "柏林") { crashForTest = true; } else { crashForTest = false; } } #endif try { return(doSetResults(lookupId, entryProvider, results, script)); } finally { if (entryProvider != null) { entryProvider.Dispose(); } } }
/// <summary> /// Initializes tooltip provider for "search language" or "script" button. /// </summary> /// <param name="button">The actual button.</param> /// <param name="isLang">If true, this is tooltip for "search lang"; otherwise, for "script".</param> /// <param name="tprov">Localized UI strings provider.</param> /// <param name="script">Current search script.</param> /// <param name="lang">Current search language.</param> /// <param name="needleHeight">Needle's height at today's scaling.</param> public SearchOptionsTooltip(ZenGradientButton button, bool isLang, ITextProvider tprov, SearchScript script, SearchLang lang, int needleHeight, int boxRight) { this.button = button; this.needleHeight = needleHeight; this.topOrSide = -boxRight; if (isLang) { if (lang == SearchLang.Chinese) { text = tprov.GetString("LangZhoTooltip"); } else { text = tprov.GetString("LangTrgTooltip"); } } else { if (script == SearchScript.Simplified) { text = tprov.GetString("ScriptSimpTooltip"); } else if (script == SearchScript.Traditional) { text = tprov.GetString("ScriptTradTooltip"); } else { text = tprov.GetString("ScriptBothTooltip"); } } }
/// <summary> /// Gets the sense formatted in HTML. /// </summary> public static string GetSenseHtml(CedictSense sense, SearchScript script) { StringBuilder sb = new StringBuilder(); sb.Append(templateOuter); string htmlSense = templateSense.Replace("{sense}", getSenseHtmlPure(null, sense, script)); htmlSense = templateSenseSingleOpen + htmlSense + templateSenseSingleClose; sb.Replace("{body}", htmlSense); return(sb.ToString()); }
private int annotateFrom(MySqlCommand cmdSelBinary10, Dictionary <int, CedictEntry> loadedEntries, byte[] buf, string query, bool simp, int start, int farthestEnd, List <CedictAnnotation> anns) { // Get candidates List <Index.AnnCandidate> cands = index.GetAnnotationCandidates(query, simp, start, farthestEnd); if (cands.Count == 0) { return(-1); } // Try to verify longest candidates, then increasingly shorter ones int cix = 0; for (int length = cands[0].Length; length > 0; --length) { List <int> idList = new List <int>(); while (cix < cands.Count && cands[cix].Length == length) { idList.Add(cands[cix].EntryId); ++cix; } loadMissingEntries(cmdSelBinary10, loadedEntries, buf, idList); SearchScript scr = simp ? SearchScript.Simplified : SearchScript.Traditional; string expected = query.Substring(start, length); bool foundAny = false; foreach (int id in idList) { CedictEntry entry = loadedEntries[id]; string hw = simp ? entry.ChSimpl : entry.ChTrad; if (hw == expected) { CedictAnnotation ann = new CedictAnnotation(id, entry, scr, start, length); anns.Add(ann); foundAny = true; } } if (foundAny) { return(start + length); } } return(-1); }
/// <summary> /// Event handler: script selector button clicked. /// </summary> private void onSimpTrad(ZenControlBase sender) { // Next in row int scri = (int)searchScript; ++scri; if (scri > 2) { scri = 0; } searchScript = (SearchScript)scri; // Update button simpTradChanged(); // Change character picker's font // Only if it really changes - triggers calibration ctrlCharPicker.FontScript = searchScript == SearchScript.Traditional ? IdeoScript.Trad : IdeoScript.Simp; // Re-recognize strokes, if there are any startNewCharRecog(writingPad.Strokes); }
/// <summary> /// Verifies which candidates really fit query's indicated substring. /// </summary> private List <CedictAnnotation> doVerifyAnns(BinReader br, string query, bool simp, int start, int length, HashSet <int> cands) { List <CedictAnnotation> vers = new List <CedictAnnotation>(); SearchScript scr = simp ? SearchScript.Simplified : SearchScript.Traditional; string expected = query.Substring(start, length); foreach (var ix in cands) { br.Position = ix; CedictEntry entry = new CedictEntry(br); string hw = simp ? entry.ChSimpl : entry.ChTrad; if (hw == expected) { CedictAnnotation ann = new CedictAnnotation(ix, scr, start, length); vers.Add(ann); } } return(vers); }
/// <summary> /// Retrieves entries for a Chinese search expression (pinyin vs. hanzi auto-detected) /// </summary> private List <CedictResult> doChineseLookup(BinReader br, string query, SearchScript script) { List <CedictResult> res = new List <CedictResult>(); // If query string has ideographic characters, do hanzi looup if (hasIdeo(query)) { res = doHanziLookupHead(br, query, script); } // Otherwise, do pinyin lookup else { // Parse pinyin query string List <PinyinSyllable> sylls = ParsePinyinQuery(query); // Lookup res = doPinyinLookupHead(br, sylls); } // Done return(res); }
/// <summary> /// Ctor: sets the link's query string based on available information. /// </summary> /// <param name="simp">Simplified Hanzi, or empty string.</param> /// <param name="trad">Traditional Hanzi, or empty string.</param> /// <param name="pinyin">Pinyin, or empty string.</param> /// <param name="script">Search script (to choose simp/trad Hanzi, if available).</param> public LinkArea(string simp, string trad, string pinyin, SearchScript script) { if (simp == string.Empty && trad != string.Empty) { simp = trad; } else if (trad == string.Empty && simp != string.Empty) { trad = simp; } // We have hanzi. Use that. if (simp != string.Empty) { QueryString = script == SearchScript.Traditional ? trad : simp; } // No hanzi. Must have pinyin, use that. else { QueryString = pinyin; } }
/// <summary> /// Ctor: takes data to display. /// </summary> /// <param name="owner">Zen control that owns me.</param> /// <param name="tprov">Localized display text provider.</param> /// <param name="lookupThroughLink">Delegate to call when user initiates lookup by clicking on a link.</param> /// <param name="getEntry">Delegate to call when an entry must be retrieved (for "copy" context menu).</param> /// <param name="entryProvider">Dictionary entry provider.</param> /// <param name="cr">The lookup result this control will show.</param> /// <param name="maxHeadLength">Longest headword in full results list.</param> /// <param name="script">Scripts to show in headword.</param> /// <param name="odd">Odd/even position in list, for alternating BG color.</param> public OneResultControl(ZenControlBase owner, float scale, ITextProvider tprov, LookupThroughLinkDelegate lookupThroughLink, ParentPaintDelegate parentPaint, GetEntryDelegate getEntry, ICedictEntryProvider entryProvider, CedictResult cr, SearchScript script, bool last) : base(owner) { this.scale = scale; this.tprov = tprov; this.lookupThroughLink = lookupThroughLink; this.parentPaint = parentPaint; this.getEntry = getEntry; this.entry = entryProvider.GetEntry(cr.EntryId); this.res = cr; this.analyzedScript = script; this.last = last; padLeft = (int)(5.0F * scale); padTop = (int)(4.0F * scale); padBottom = (int)(8.0F * scale); padMid = (int)(20.0F * scale); padRight = (int)(10.0F * scale); }
/// <summary> /// Gets the entry formatted in HTML. /// </summary> public static string GetHtml(ITextProvider tprov, CedictEntry entry, SearchScript script) { StringBuilder bodyHtml = new StringBuilder(); // Are we showing one or two Hanzi headwords? string hanzi1 = script == SearchScript.Traditional ? entry.ChTrad : entry.ChSimpl; string hanzi2 = null; if (script == SearchScript.Both && entry.ChSimpl != entry.ChTrad) { hanzi2 = entry.ChTrad; } // Find simplest possible template, work with that // Only one hanzi, no longer than 2 chars, only one sense bool mustDoSenses = true; if (hanzi2 == null && hanzi1.Length <= 2 && entry.SenseCount == 1) { mustDoSenses = false; bodyHtml.Append(template1); bodyHtml.Replace("{hanzi}", escape(hanzi1)); bodyHtml.Replace("{pinyin}", escape(GetPinyinString(entry.GetPinyinForDisplay(true)))); bodyHtml.Replace("{sense}", getSenseHtmlPure(tprov, entry.GetSenseAt(0), script)); } // Only one script, no more than 6 chars else if (hanzi2 == null && hanzi1.Length <= 6) { bodyHtml.Append(template2); bodyHtml.Replace("{hanzi}", escape(hanzi1)); bodyHtml.Replace("{pinyin}", escape(GetPinyinString(entry.GetPinyinForDisplay(true)))); } // Only one script else if (hanzi2 == null) { bodyHtml.Append(template3); bodyHtml.Replace("{hanzi}", escape(hanzi1)); bodyHtml.Replace("{pinyin}", escape(GetPinyinString(entry.GetPinyinForDisplay(true)))); } // Everything else: very full-fledged entry else { bodyHtml.Append(template4); bodyHtml.Replace("{hanzi1}", escape(hanzi1)); bodyHtml.Replace("{hanzi2}", escape(hanzi2)); bodyHtml.Replace("{pinyin}", escape(GetPinyinString(entry.GetPinyinForDisplay(true)))); } // In all but the first, simplest case, dealing with senses is the same if (mustDoSenses) { StringBuilder sbSenses = new StringBuilder(); foreach (CedictSense sense in entry.Senses) { string senseHtml = ""; if (!sense.Domain.EqualsPlainText("CL:")) { senseHtml += templateDiamond; senseHtml += " "; } senseHtml += getSenseHtmlPure(tprov, sense, script); senseHtml = templateSense.Replace("{sense}", senseHtml); sbSenses.Append(senseHtml); } bodyHtml.Replace("{senses}", sbSenses.ToString()); } // Assemble the whole HTML StringBuilder sb = new StringBuilder(); sb.Append(templateOuter); sb.Replace("{body}", bodyHtml.ToString()); // Purge new lines and tabs: this avoids extra spaces e.g. when pasting into Word sb.Replace("\r\n", ""); sb.Replace("\t", ""); // Done return(sb.ToString()); }
/// <summary> /// Retrieves entries (sorted) whose headword contains hanzi from search expression. /// </summary> List<CedictResult> doHanziLookupHead(BinReader br, string query, SearchScript script) { // Get every character once - we ignore repeats HashSet<char> queryChars = new HashSet<char>(); foreach (char c in query) queryChars.Add(c); // Map from keys (entry positions) to # of query chars found in entry Dictionary<int, int> posToCountSimp = new Dictionary<int, int>(); Dictionary<int, int> posToCountTrad = new Dictionary<int, int>(); // Look at each character's entry position vector, increment counts foreach (char c in queryChars) { // If there's a hanzi that's not in index, we'll sure have not hits! if (!index.IdeoIndex.ContainsKey(c)) return new List<CedictResult>(); IdeoIndexItem iii = index.IdeoIndex[c]; // Count separately for simplified and traditional foreach (int pos in iii.EntriesHeadwordSimp) { if (posToCountSimp.ContainsKey(pos)) ++posToCountSimp[pos]; else posToCountSimp[pos] = 1; } foreach (int pos in iii.EntriesHeadwordTrad) { if (posToCountTrad.ContainsKey(pos)) ++posToCountTrad[pos]; else posToCountTrad[pos] = 1; } } // Get positions that contain all chars from query HashSet<int> matchingPositions = new HashSet<int>(); foreach (var x in posToCountSimp) if (x.Value == queryChars.Count) matchingPositions.Add(x.Key); foreach (var x in posToCountTrad) if (x.Value == queryChars.Count) matchingPositions.Add(x.Key); // Now fetch and verify results List<ResWithEntry> resWE = doLoadVerifyHanzi(br, matchingPositions, query, script); // Sort hanzi results resWE.Sort((a, b) => hrComp(a, b)); // Done. List<CedictResult> res = new List<CedictResult>(resWE.Capacity); for (int i = 0; i != resWE.Count; ++i) res.Add(resWE[i].Res); return res; }
/// <summary> /// Retrieves hanzi lookup candidates, verifies actual presence of search expression in headword. /// </summary> List <ResWithEntry> doLoadVerifyHanzi(BinReader br, IEnumerable <int> poss, string query, SearchScript script) { List <ResWithEntry> resList = new List <ResWithEntry>(); // Yes, we only open our file on-demand // But we do this within each lookup's scope, so lookup stays thread-safe // Look at each entry: load, verify, keep or drop foreach (int pos in poss) { // Load up entry from file br.Position = pos; CedictEntry entry = new CedictEntry(br); // Figure out position/length of query string in simplified and traditional headwords int hiliteStart = -1; int hiliteLength = 0; hiliteStart = entry.ChSimpl.IndexOf(query); if (hiliteStart != -1) { hiliteLength = query.Length; } // If not found in simplified, check in traditional if (hiliteLength == 0) { hiliteStart = entry.ChTrad.IndexOf(query); if (hiliteStart != -1) { hiliteLength = query.Length; } } // Entry is a keeper if either source or target headword contains query if (hiliteLength != 0) { // Drop if there's any unprintable hanzi if (!areHanziCovered(entry)) { continue; } // TO-DO: indicate wrong script in result CedictResult res = new CedictResult(CedictResult.SimpTradWarning.None, pos, entry.HanziPinyinMap, hiliteStart, hiliteLength); ResWithEntry resWE = new ResWithEntry(res, entry); resList.Add(resWE); } } return(resList); }
/// <summary> /// Retrieves entries (sorted) whose headword contains hanzi from search expression. /// </summary> List <CedictResult> doHanziLookupHead(BinReader br, string query, SearchScript script) { // Get every character once - we ignore repeats HashSet <char> queryChars = new HashSet <char>(); foreach (char c in query) { queryChars.Add(c); } // Map from keys (entry positions) to # of query chars found in entry Dictionary <int, int> posToCountSimp = new Dictionary <int, int>(); Dictionary <int, int> posToCountTrad = new Dictionary <int, int>(); // Look at each character's entry position vector, increment counts foreach (char c in queryChars) { // If there's a hanzi that's not in index, we'll sure have not hits! if (!index.IdeoIndex.ContainsKey(c)) { return(new List <CedictResult>()); } IdeoIndexItem iii = index.IdeoIndex[c]; // Count separately for simplified and traditional foreach (var iep in iii.EntriesHeadwordSimp) { if (posToCountSimp.ContainsKey(iep.EntryIdx)) { ++posToCountSimp[iep.EntryIdx]; } else { posToCountSimp[iep.EntryIdx] = 1; } } foreach (var iep in iii.EntriesHeadwordTrad) { if (posToCountTrad.ContainsKey(iep.EntryIdx)) { ++posToCountTrad[iep.EntryIdx]; } else { posToCountTrad[iep.EntryIdx] = 1; } } } // Get positions that contain all chars from query HashSet <int> matchingPositions = new HashSet <int>(); foreach (var x in posToCountSimp) { if (x.Value == queryChars.Count) { matchingPositions.Add(x.Key); } } foreach (var x in posToCountTrad) { if (x.Value == queryChars.Count) { matchingPositions.Add(x.Key); } } // Now fetch and verify results List <ResWithEntry> resWE = doLoadVerifyHanzi(br, matchingPositions, query, script); // Sort hanzi results resWE.Sort((a, b) => hrComp(a, b)); // Done. List <CedictResult> res = new List <CedictResult>(resWE.Capacity); for (int i = 0; i != resWE.Count; ++i) { res.Add(resWE[i].Res); } return(res); }
/// <summary> /// Event handler: script selector button clicked. /// </summary> private void onSimpTrad(ZenControlBase sender) { // Next in row int scri = (int)searchScript; ++scri; if (scri > 2) scri = 0; searchScript = (SearchScript)scri; // Update button simpTradChanged(); // Change character picker's font // Only if it really changes - triggers calibration ctrlCharPicker.FontScript = searchScript == SearchScript.Traditional ? IdeoScript.Trad : IdeoScript.Simp; // Re-recognize strokes, if there are any startNewCharRecog(writingPad.Strokes); }
/// <summary> /// Initializes tooltip provider for "search language" or "script" button. /// </summary> /// <param name="button">The actual button.</param> /// <param name="isLang">If true, this is tooltip for "search lang"; otherwise, for "script".</param> /// <param name="tprov">Localized UI strings provider.</param> /// <param name="script">Current search script.</param> /// <param name="lang">Current search language.</param> /// <param name="needleHeight">Needle's height at today's scaling.</param> public SearchOptionsTooltip(ZenGradientButton button, bool isLang, ITextProvider tprov, SearchScript script, SearchLang lang, int needleHeight, int boxRight) { this.button = button; this.needleHeight = needleHeight; this.topOrSide = -boxRight; if (isLang) { if (lang == SearchLang.Chinese) text = tprov.GetString("LangZhoTooltip"); else text = tprov.GetString("LangTrgTooltip"); } else { if (script == SearchScript.Simplified) text = tprov.GetString("ScriptSimpTooltip"); else if (script == SearchScript.Traditional) text = tprov.GetString("ScriptTradTooltip"); else text = tprov.GetString("ScriptBothTooltip"); } }
/// <summary> /// Ctor: init. /// </summary> /// <param name="cmdTriggered">Delegate that will be called when a command is issued.</param> /// <param name="entry">Cedict entry to fetch clipboard data from.</param> /// <param name="senseIX">Index of sense over which user right-clicked, or -1.</param> /// <param name="script">Search script (so two Hanzi items are shown if needed).</param> public ResultsCtxtControl(CommandTriggeredDelegate cmdTriggered, ITextProvider tprov, CedictEntry entry, int senseIx, SearchScript script) { this.cmdTriggered = cmdTriggered; this.tprov = tprov; this.entry = entry; this.senseIx = senseIx; this.script = script; InitializeComponent(); BackColor = ZenParams.BorderColor; pnlTop.BackColor = ZenParams.WindowColor; tblFull.BackColor = ZenParams.WindowColor; tblZho.BackColor = ZenParams.WindowColor; tblSense.BackColor = ZenParams.WindowColor; // Display strings string title = tprov.GetString("CtxtCopyTitle"); string fullFormatted, fullCedict, hanzi1, hanzi2, pinyin, sense; getDisplayStrings(tprov, senseIx, out fullFormatted, out fullCedict, out hanzi1, out hanzi2, out pinyin, out sense); lblFullFormatted.Text = fullFormatted; lblFullCedict.Text = fullCedict; lblHanzi1.Text = hanzi1; lblHanzi2.Text = hanzi2; lblPinyin.Text = pinyin; lblSense.Text = sense; // Margin/border tweaks: 1px also at higher DPIs tblLayout.Location = new Point(1, 1); pnlTop.Margin = new Padding(0, 0, 0, 1); tblLayout.RowStyles[1].Height = pnlTop.Height + 1; tblFull.Margin = new Padding(0, 0, 0, 1); tblLayout.RowStyles[1].Height = tblFull.Height + 1; tblZho.Margin = new Padding(0, 0, 0, 1); tblLayout.RowStyles[2].Height = tblZho.Height + 1; tblSense.Margin = new Padding(0, 0, 0, 0); tblLayout.RowStyles[3].Height = tblSense.Height; tblLayout.Height = tblSense.Bottom; // Hide rows we don't need: second hanzi if (hanzi2 == null) { int hHanzi2 = lblHanzi2.Height; tblZho.Controls.Remove(lblHanzi2); lblPinyin.Top = lblHanzi2.Top; lblHanzi2.Dispose(); lblHanzi2 = null; tblZho.Controls.Remove(lblPinyin); tblZho.Controls.Add(lblPinyin, 0, 2); tblZho.RowCount -= 1; tblZho.RowStyles.RemoveAt(2); tblZho.Height -= hHanzi2; tblLayout.RowStyles[2].Height -= hHanzi2; tblLayout.Height -= hHanzi2; } // Sense if (sense == null) { int hSense = tblSense.Height; tblLayout.Controls.Remove(tblSense); tblSense.Dispose(); tblSense = null; tblLayout.RowStyles.RemoveAt(tblLayout.RowStyles.Count - 1); tblLayout.RowCount -= 1; tblLayout.Height -= hSense + 1; } // Label collection for hover int lblCount = 6; if (lblHanzi2 == null) --lblCount; if (lblSense == null) --lblCount; lblColl = new Label[lblCount]; lblColl[0] = lblFullFormatted; lblColl[1] = lblFullCedict; lblColl[2] = lblHanzi1; int ix = 3; if (lblHanzi2 != null) { lblColl[ix] = lblHanzi2; ++ix; } lblColl[ix] = lblPinyin; ++ix; if (lblSense != null) { lblColl[ix] = lblSense; ++ix; } // Event handling for hover tblFull.CellPaint += onTblLayoutCellPaint; tblZho.CellPaint += onTblLayoutCellPaint; if (tblSense != null) tblSense.CellPaint += onTblLayoutCellPaint; }
/// <summary> /// Gets the HTML for a single sense, not including enclosing paragraph etc., only inline markup. /// </summary> /// <param name="tprov">Text provider if meta-labels (e.g. "Classifier") are to be included. If null, they are stripped.</param> private static string getSenseHtmlPure(ITextProvider tprov, CedictSense sense, SearchScript script) { StringBuilder sb = new StringBuilder(); string strDomain = HybridToHtml(sense.Domain, script); string strEquiv = HybridToHtml(sense.Equiv, script); string strNote = HybridToHtml(sense.Note, script); if (sense.Domain != HybridText.Empty) { if (sense.Domain.EqualsPlainText("CL:")) { if (tprov != null) { sb.Append(templateItalicsOpen); sb.Append(escape(tprov.GetString("ResultCtrlClassifier")) + " "); sb.Append(templateItalicsClose); } } else { sb.Append(templateItalicsOpen); sb.Append(strDomain); sb.Append(templateItalicsClose); } } if (sense.Domain != HybridText.Empty && !sense.Domain.EqualsPlainText("CL:")) if (sense.Equiv != HybridText.Empty || sense.Note != HybridText.Empty) sb.Append(' '); sb.Append(strEquiv); if (sense.Equiv != HybridText.Empty && sense.Note != HybridText.Empty) sb.Append(' '); if (sense.Note != HybridText.Empty) { sb.Append(templateItalicsOpen); sb.Append(strNote); sb.Append(templateItalicsClose); } // Done return sb.ToString(); }
/// <summary> /// Converts a hybrid text to HTML (marking up hanzi+pinyin sections). /// </summary> public static string HybridToHtml(HybridText ht, SearchScript script) { StringBuilder sb = new StringBuilder(); bool first = true; for (int i = 0; i != ht.RunCount; ++i) { TextRun tr = ht.GetRunAt(i); if (tr is TextRunLatin) { string strRun = tr.GetPlainText(); if (!first && strRun != string.Empty && !char.IsPunctuation(strRun[0])) sb.Append(' '); sb.Append(strRun); } else { if (!first) sb.Append(' '); TextRunZho trz = tr as TextRunZho; string hanzi1 = (script == SearchScript.Traditional) ? trz.Trad : trz.Simp; if (string.IsNullOrEmpty(hanzi1)) hanzi1 = null; string hanzi2 = null; if (hanzi1 != null && script == SearchScript.Both && !string.IsNullOrEmpty(trz.Trad)) hanzi2 = trz.Trad; if (hanzi1 != null) hanzi1 = escape(hanzi1); if (hanzi2 != null) hanzi2 = escape(hanzi2); if (hanzi1 != null || hanzi2 != null) sb.Append(templateSenseHanziOpen); if (hanzi1 != null) sb.Append(hanzi1); if (hanzi2 != null) { sb.Append(' '); sb.Append(templateBullet); sb.Append(' '); sb.Append(hanzi2); } if (hanzi1 != null || hanzi2 != null) sb.Append(templateSenseHanziClose); if (trz.Pinyin != null) { if (hanzi1 != null) sb.Append(' '); sb.Append('['); sb.Append(escape(trz.GetPinyinInOne(true))); sb.Append(']'); } } first = false; } return sb.ToString(); }
/// <summary> /// Gets the sense formatted in HTML. /// </summary> public static string GetSenseHtml(CedictSense sense, SearchScript script) { StringBuilder sb = new StringBuilder(); sb.Append(templateOuter); string htmlSense = templateSense.Replace("{sense}", getSenseHtmlPure(null, sense, script)); htmlSense = templateSenseSingleOpen + htmlSense + templateSenseSingleClose; sb.Replace("{body}", htmlSense); return sb.ToString(); }
/// <summary> /// Find entries that match the search expression. /// </summary> /// <param name="query">The query string, as entered by the user.</param> /// <param name="script">For hanzi lookup: simplified, traditional or both.</param> /// <param name="lang">Chinese or target language (English).</param> /// <returns>The lookup result.</returns> public CedictLookupResult Lookup(string query, SearchScript script, SearchLang lang) { List<CedictResult> res = new List<CedictResult>(); // BinReader: I own it until I successfully return results to caller. BinReader br = new BinReader(dictFileName); EntryProvider ep = new EntryProvider(br); try { // Try first in language requested by user // If no results that way, try in opposite language // Override if lookup in opposite language is successful if (lang == SearchLang.Chinese) { res = doChineseLookup(br, query, script); // We got fish if (res.Count > 0) return new CedictLookupResult(ep, new ReadOnlyCollection<CedictResult>(res), lang); // OK, try opposite (target) res = doTargetLookup(br, query); // We got fish: override if (res.Count > 0) return new CedictLookupResult(ep, new ReadOnlyCollection<CedictResult>(res), SearchLang.Target); } else { res = doTargetLookup(br, query); // We got fish if (res.Count > 0) return new CedictLookupResult(ep, new ReadOnlyCollection<CedictResult>(res), lang); // OK, try opposite (target) res = doChineseLookup(br, query, script); // We got fish: override if (res.Count > 0) return new CedictLookupResult(ep, new ReadOnlyCollection<CedictResult>(res), SearchLang.Chinese); } // Sorry, no results, no override return new CedictLookupResult(ep, new ReadOnlyCollection<CedictResult>(res), lang); } catch { br.Dispose(); throw; } }
/// <summary> /// Retrieves hanzi lookup candidates, verifies actual presence of search expression in headword. /// </summary> List<ResWithEntry> doLoadVerifyHanzi(BinReader br, IEnumerable<int> poss, string query, SearchScript script) { List<ResWithEntry> resList = new List<ResWithEntry>(); // Yes, we only open our file on-demand // But we do this within each lookup's scope, so lookup stays thread-safe // Look at each entry: load, verify, keep or drop foreach (int pos in poss) { // Load up entry from file br.Position = pos; CedictEntry entry = new CedictEntry(br); // Figure out position/length of query string in simplified and traditional headwords int hiliteStart = -1; int hiliteLength = 0; hiliteStart = entry.ChSimpl.IndexOf(query); if (hiliteStart != -1) hiliteLength = query.Length; // If not found in simplified, check in traditional if (hiliteLength == 0) { hiliteStart = entry.ChTrad.IndexOf(query); if (hiliteStart != -1) hiliteLength = query.Length; } // Entry is a keeper if either source or target headword contains query if (hiliteLength != 0) { // Drop if there's any unprintable hanzi if (!areHanziCovered(entry)) continue; // TO-DO: indicate wrong script in result CedictResult res = new CedictResult(CedictResult.SimpTradWarning.None, pos, entry.HanziPinyinMap, hiliteStart, hiliteLength); ResWithEntry resWE = new ResWithEntry(res, entry); resList.Add(resWE); } } return resList; }
/// <summary> /// Displays the received results, discarding existing data. /// </summary> /// <param name="lookupId">ID of lookup whose results are shown. If ID is smaller than last seen value, we don't show results.</param> /// <param name="entryProvider">The entry provider; ownership passed by caller to me.</param> /// <param name="results">Cedict lookup results to show.</param> /// <param name="script">Defines which script(s) to show.</param> /// <returns>True if results got shown; false if they're discarded because newer results are already on display.</returns> public bool SetResults(int lookupId, ICedictEntryProvider entryProvider, ReadOnlyCollection<CedictResult> results, SearchScript script) { #if DEBUG // Make us crash at bottom if first result "柏林" (comes up for "bolin") if (results.Count > 0) { CedictEntry entry = entryProvider.GetEntry(results[0].EntryId); if (entry.ChSimpl == "柏林") crashForTest = true; else crashForTest = false; } #endif try { return doSetResults(lookupId, entryProvider, results, script); } finally { if (entryProvider != null) entryProvider.Dispose(); } }
/// <summary> /// Gets the entry formatted in HTML. /// </summary> public static string GetHtml(ITextProvider tprov, CedictEntry entry, SearchScript script) { StringBuilder bodyHtml = new StringBuilder(); // Are we showing one or two Hanzi headwords? string hanzi1 = script == SearchScript.Traditional ? entry.ChTrad : entry.ChSimpl; string hanzi2 = null; if (script == SearchScript.Both && entry.ChSimpl != entry.ChTrad) hanzi2 = entry.ChTrad; // Find simplest possible template, work with that // Only one hanzi, no longer than 2 chars, only one sense bool mustDoSenses = true; if (hanzi2 == null && hanzi1.Length <= 2 && entry.SenseCount == 1) { mustDoSenses = false; bodyHtml.Append(template1); bodyHtml.Replace("{hanzi}", escape(hanzi1)); bodyHtml.Replace("{pinyin}", escape(GetPinyinString(entry.GetPinyinForDisplay(true)))); bodyHtml.Replace("{sense}", getSenseHtmlPure(tprov, entry.GetSenseAt(0), script)); } // Only one script, no more than 6 chars else if (hanzi2 == null && hanzi1.Length <= 6) { bodyHtml.Append(template2); bodyHtml.Replace("{hanzi}", escape(hanzi1)); bodyHtml.Replace("{pinyin}", escape(GetPinyinString(entry.GetPinyinForDisplay(true)))); } // Only one script else if (hanzi2 == null) { bodyHtml.Append(template3); bodyHtml.Replace("{hanzi}", escape(hanzi1)); bodyHtml.Replace("{pinyin}", escape(GetPinyinString(entry.GetPinyinForDisplay(true)))); } // Everything else: very full-fledged entry else { bodyHtml.Append(template4); bodyHtml.Replace("{hanzi1}", escape(hanzi1)); bodyHtml.Replace("{hanzi2}", escape(hanzi2)); bodyHtml.Replace("{pinyin}", escape(GetPinyinString(entry.GetPinyinForDisplay(true)))); } // In all but the first, simplest case, dealing with senses is the same if (mustDoSenses) { StringBuilder sbSenses = new StringBuilder(); foreach (CedictSense sense in entry.Senses) { string senseHtml = ""; if (!sense.Domain.EqualsPlainText("CL:")) { senseHtml += templateDiamond; senseHtml += " "; } senseHtml += getSenseHtmlPure(tprov, sense, script); senseHtml = templateSense.Replace("{sense}", senseHtml); sbSenses.Append(senseHtml); } bodyHtml.Replace("{senses}", sbSenses.ToString()); } // Assemble the whole HTML StringBuilder sb = new StringBuilder(); sb.Append(templateOuter); sb.Replace("{body}", bodyHtml.ToString()); // Purge new lines and tabs: this avoids extra spaces e.g. when pasting into Word sb.Replace("\r\n", ""); sb.Replace("\t", ""); // Done return sb.ToString(); }
/// <summary> /// See <see cref="SetResults"/>. /// </summary> private bool doSetResults(int lookupId, ICedictEntryProvider entryProvider, ReadOnlyCollection <CedictResult> results, SearchScript script) { lock (displayIdLO) { // If we're already too late, don't bother changing display. if (displayId > lookupId) { return(false); } displayId = lookupId; // Empty result set - special handling if (results.Count == 0) { lock (resCtrlsLO) { doDisposeResultControls(); txtResCount = tprov.GetString("ResultsCountNone"); setScrollbarVisibility(false); } // Render doFade(false); MakeMePaint(false, RenderMode.Invalidate); return(true); } } // Decide if we first try with scrollbar visible or not // This is a very rough heuristics (10 results or more), but doesn't matter // Recalc costs much if there are many results, and the number covers that safely bool sbarVisible = results.Count > 10; // Content rectangle height and width int cw, ch; getContentSize(sbarVisible, out cw, out ch); // Create new result controls. At this point, not overwriting old ones! // This is the cycle that takes *long*. List <OneResultControl> newCtrls = new List <OneResultControl>(results.Count); int y = 0; using (Bitmap bmp = new Bitmap(1, 1)) using (Graphics g = Graphics.FromImage(bmp)) { bool canceled = false; for (int rix = 0; rix != results.Count; ++rix) { CedictResult cr = results[rix]; OneResultControl orc = new OneResultControl(null, Scale, tprov, onLookupFromCtrl, onPaintFromCtrl, onGetEntry, entryProvider, cr, script, rix == results.Count - 1); orc.Analyze(g, cw); // Cannot use RelLocation b/c control has no parent yet orc.AbsLocation = new Point(AbsLeft + 1, AbsTop + y + 1); y += orc.Height; newCtrls.Add(orc); // At any point, if we realize lookup ID has changed, we stop // This can happen if a later, quick lookup completes and shows results before us // Checking integers is atomic, no locking if (displayId > lookupId) { canceled = true; break; } } if (canceled) { foreach (OneResultControl orc in newCtrls) { orc.Dispose(); } return(false); } } // OK, last chance to change our mind about showing results. // The rest is synchronized - but it's also fast lock (displayIdLO) { if (displayId > lookupId) { return(false); } displayId = lookupId; // Rest must be invoked on GUI. Otherwise, as we're adding children, // Collections are modified that are also accessed by paint in a resize event handler etc. InvokeOnForm((MethodInvoker) delegate { // Stop any scrolling that may be going on. Cannot scroll what's being replaced. if (sbar.Parent == this) { sbar.StopAnyScrolling(); } // Prevent any painting from worker threads - also accesses collection we're changing lock (resCtrlsLO) { // Get rid of old result controls, remember/own new ones doDisposeResultControls(); resCtrls = newCtrls; foreach (OneResultControl orc in resCtrls) { AddChild(orc); } // Actually show or hide scrollbar as per original decision setScrollbarVisibility(sbarVisible); // Now, by the time we're here, size may have changed // That is unlikely, but then we got to re-layout stuff int cwNew, chNew; getContentSize(sbarVisible, out cwNew, out chNew); if (cwNew != cw || chNew != ch) { reAnalyzeResultsDisplay(); } else { // Everything as big as it used to be... // Change our mind about scrollbar? cw = showOrHideScrollbar(); } } // Results count text if (resCtrls.Count == 1) { txtResCount = tprov.GetString("ResultsCountOne"); } else { txtResCount = tprov.GetString("ResultsCountN"); txtResCount = string.Format(txtResCount, resCtrls.Count); } // Update first visible control's index updateFirstVisibleIdx(); // Render doFade(false); MakeMePaint(false, RenderMode.Invalidate); }); // Done. return(true); } }
/// <summary> /// Find entries that match the search expression. /// </summary> /// <param name="query">The query string, as entered by the user.</param> /// <param name="script">For hanzi lookup: simplified, traditional or both.</param> /// <param name="lang">Chinese or target language (English).</param> /// <returns>The lookup result.</returns> public CedictLookupResult Lookup(string query, SearchScript script, SearchLang lang) { List <CedictResult> res = new List <CedictResult>(); List <CedictAnnotation> anns = new List <CedictAnnotation>(); // BinReader: I own it until I successfully return results to caller. BinReader br = new BinReader(dictFileName); EntryProvider ep = new EntryProvider(br); try { // Try first in language requested by user // If no results that way, try in opposite language // Override if lookup in opposite language is successful if (lang == SearchLang.Chinese) { res = doChineseLookup(br, query, script); // We got fish if (res.Count > 0) { return(new CedictLookupResult(ep, query, res, anns, lang)); } // Try to annotate anns = doAnnotate(br, query); if (anns.Count > 0) { return(new CedictLookupResult(ep, query, res, anns, lang)); } // OK, try opposite (target) res = doTargetLookup(br, query); // We got fish: override if (res.Count > 0) { return(new CedictLookupResult(ep, query, res, anns, SearchLang.Target)); } } else { res = doTargetLookup(br, query); // We got fish if (res.Count > 0) { return(new CedictLookupResult(ep, query, res, anns, lang)); } // OK, try opposite (target) res = doChineseLookup(br, query, script); // We got fish: override if (res.Count > 0) { return(new CedictLookupResult(ep, query, res, anns, SearchLang.Chinese)); } // Try to annotate anns = doAnnotate(br, query); if (anns.Count > 0) { return(new CedictLookupResult(ep, query, res, anns, SearchLang.Chinese)); } } // Sorry, no results, no override return(new CedictLookupResult(ep, query, res, anns, lang)); } catch { br.Dispose(); throw; } }
/// <summary> /// Ctor: init. /// </summary> /// <param name="cmdTriggered">Delegate that will be called when a command is issued.</param> /// <param name="entry">Cedict entry to fetch clipboard data from.</param> /// <param name="senseIX">Index of sense over which user right-clicked, or -1.</param> /// <param name="script">Search script (so two Hanzi items are shown if needed).</param> public ResultsCtxtControl(CommandTriggeredDelegate cmdTriggered, ITextProvider tprov, CedictEntry entry, int senseIx, SearchScript script) { this.cmdTriggered = cmdTriggered; this.tprov = tprov; this.entry = entry; this.senseIx = senseIx; this.script = script; InitializeComponent(); BackColor = ZenParams.BorderColor; pnlTop.BackColor = ZenParams.WindowColor; tblFull.BackColor = ZenParams.WindowColor; tblZho.BackColor = ZenParams.WindowColor; tblSense.BackColor = ZenParams.WindowColor; // Display strings string title = tprov.GetString("CtxtCopyTitle"); string fullFormatted, fullCedict, hanzi1, hanzi2, pinyin, sense; getDisplayStrings(tprov, senseIx, out fullFormatted, out fullCedict, out hanzi1, out hanzi2, out pinyin, out sense); lblFullFormatted.Text = fullFormatted; lblFullCedict.Text = fullCedict; lblHanzi1.Text = hanzi1; lblHanzi2.Text = hanzi2; lblPinyin.Text = pinyin; lblSense.Text = sense; // Margin/border tweaks: 1px also at higher DPIs tblLayout.Location = new Point(1, 1); pnlTop.Margin = new Padding(0, 0, 0, 1); tblLayout.RowStyles[1].Height = pnlTop.Height + 1; tblFull.Margin = new Padding(0, 0, 0, 1); tblLayout.RowStyles[1].Height = tblFull.Height + 1; tblZho.Margin = new Padding(0, 0, 0, 1); tblLayout.RowStyles[2].Height = tblZho.Height + 1; tblSense.Margin = new Padding(0, 0, 0, 0); tblLayout.RowStyles[3].Height = tblSense.Height; tblLayout.Height = tblSense.Bottom; // Hide rows we don't need: second hanzi if (hanzi2 == null) { int hHanzi2 = lblHanzi2.Height; tblZho.Controls.Remove(lblHanzi2); lblPinyin.Top = lblHanzi2.Top; lblHanzi2.Dispose(); lblHanzi2 = null; tblZho.Controls.Remove(lblPinyin); tblZho.Controls.Add(lblPinyin, 0, 2); tblZho.RowCount -= 1; tblZho.RowStyles.RemoveAt(2); tblZho.Height -= hHanzi2; tblLayout.RowStyles[2].Height -= hHanzi2; tblLayout.Height -= hHanzi2; } // Sense if (sense == null) { int hSense = tblSense.Height; tblLayout.Controls.Remove(tblSense); tblSense.Dispose(); tblSense = null; tblLayout.RowStyles.RemoveAt(tblLayout.RowStyles.Count - 1); tblLayout.RowCount -= 1; tblLayout.Height -= hSense + 1; } // Label collection for hover int lblCount = 6; if (lblHanzi2 == null) { --lblCount; } if (lblSense == null) { --lblCount; } lblColl = new Label[lblCount]; lblColl[0] = lblFullFormatted; lblColl[1] = lblFullCedict; lblColl[2] = lblHanzi1; int ix = 3; if (lblHanzi2 != null) { lblColl[ix] = lblHanzi2; ++ix; } lblColl[ix] = lblPinyin; ++ix; if (lblSense != null) { lblColl[ix] = lblSense; ++ix; } // Event handling for hover tblFull.CellPaint += onTblLayoutCellPaint; tblZho.CellPaint += onTblLayoutCellPaint; if (tblSense != null) { tblSense.CellPaint += onTblLayoutCellPaint; } }
/// <summary> /// Ctor. /// </summary> public LookupControl(ZenControlBase owner, ICedictEngineFactory dictFact, ITextProvider tprov) : base(owner) { this.dictFact = dictFact; this.tprov = tprov; padding = (int)Math.Round(5.0F * Scale); // Init search language and script from user settings searchLang = AppSettings.SearchLang; searchScript = AppSettings.SearchScript; // Init HanziLookup fsStrokes = new FileStream(Magic.StrokesFileName, FileMode.Open, FileAccess.Read); brStrokes = new BinaryReader(fsStrokes); strokesData = new StrokesDataSource(brStrokes); // Writing pad writingPad = new WritingPad(this, tprov); writingPad.RelLocation = new Point(padding, padding); writingPad.LogicalSize = new Size(200, 200); writingPad.StrokesChanged += writingPad_StrokesChanged; // Images for buttons under writing pad; will get owned by buttons, not that it matters. Assembly a = Assembly.GetExecutingAssembly(); var imgStrokesClear = Image.FromStream(a.GetManifestResourceStream("ZD.Gui.Resources.strokes-clear.png")); var imgStrokesUndo = Image.FromStream(a.GetManifestResourceStream("ZD.Gui.Resources.strokes-undo.png")); // Clear and undo buttons under writing pad. float leftBtnWidth = writingPad.Width / 2 + 1; float btnHeight = 22.0F * Scale; // -- btnClearWritingPad = new ZenGradientButton(this); btnClearWritingPad.RelLocation = new Point(writingPad.RelLeft, writingPad.RelBottom - 1); btnClearWritingPad.Size = new Size((int)leftBtnWidth, (int)btnHeight); btnClearWritingPad.Text = tprov.GetString("WritingPadClear"); btnClearWritingPad.SetFont(SystemFontProvider.Instance.GetSystemFont(FontStyle.Regular, 9.0F)); btnClearWritingPad.Padding = (int)(3.0F * Scale); btnClearWritingPad.ImageExtraPadding = (int)(3.0F * Scale); btnClearWritingPad.Image = imgStrokesClear; btnClearWritingPad.Enabled = false; btnClearWritingPad.MouseClick += onClearWritingPad; // -- btnUndoStroke = new ZenGradientButton(this); btnUndoStroke.RelLocation = new Point(btnClearWritingPad.RelRight - 1, writingPad.RelBottom - 1); btnUndoStroke.Size = new Size(writingPad.RelRight - btnUndoStroke.RelLeft, (int)btnHeight); btnUndoStroke.Text = tprov.GetString("WritingPadUndo"); btnUndoStroke.SetFont(SystemFontProvider.Instance.GetSystemFont(FontStyle.Regular, 9.0F)); btnUndoStroke.Padding = (int)(3.0F * Scale); btnUndoStroke.ImageExtraPadding = (int)(1.5F * Scale); btnUndoStroke.Image = imgStrokesUndo; btnUndoStroke.Enabled = false; btnUndoStroke.MouseClick += onUndoStroke; // -- btnClearWritingPad.Tooltip = new ClearUndoTooltips(btnClearWritingPad, true, tprov, padding); btnUndoStroke.Tooltip = new ClearUndoTooltips(btnUndoStroke, false, tprov, padding); // -- // Character picker control under writing pad. ctrlCharPicker = new CharPicker(this, tprov); ctrlCharPicker.FontFam = Magic.ZhoContentFontFamily; ctrlCharPicker.FontScript = searchScript == SearchScript.Traditional ? IdeoScript.Trad : IdeoScript.Simp; ctrlCharPicker.RelLocation = new Point(padding, btnClearWritingPad.RelBottom + padding); ctrlCharPicker.LogicalSize = new Size(200, 80); ctrlCharPicker.CharPicked += onCharPicked; // Search input control at top ctrlSearchInput = new SearchInputControl(this, tprov); ctrlSearchInput.RelLocation = new Point(writingPad.RelRight + padding, padding); ctrlSearchInput.StartSearch += onStartSearch; // Tweaks for Chinese text on UI buttons // This is specific to Segoe UI and Noto Sans S Chinese fonts. float ofsZho = 0; //if (!(SystemFontProvider.Instance as ZydeoSystemFontProvider).SegoeExists) // ofsZho = Magic.ZhoButtonFontSize * Scale / 3.7F; // Script selector button to the right of search input control btnSimpTrad = new ZenGradientButton(this); btnSimpTrad.RelTop = padding; btnSimpTrad.Height = ctrlSearchInput.Height; btnSimpTrad.SetFont((SystemFontProvider.Instance as ZydeoSystemFontProvider).GetZhoButtonFont( FontStyle.Regular, Magic.ZhoButtonFontSize)); btnSimpTrad.Width = getSimpTradWidth(); btnSimpTrad.ForcedCharVertOfs = ofsZho; btnSimpTrad.RelLeft = Width - padding - btnSimpTrad.Width; btnSimpTrad.Height = ctrlSearchInput.Height; btnSimpTrad.MouseClick += onSimpTrad; // Search language selector to the right of search input control btnSearchLang = new ZenGradientButton(this); btnSearchLang.RelTop = padding; btnSearchLang.Height = ctrlSearchInput.Height; btnSearchLang.SetFont((SystemFontProvider.Instance as ZydeoSystemFontProvider).GetZhoButtonFont( FontStyle.Regular, Magic.ZhoButtonFontSize)); btnSearchLang.Width = getSearchLangWidth(); btnSearchLang.ForcedCharVertOfs = ofsZho; btnSearchLang.RelLeft = btnSimpTrad.RelLeft - padding - btnSearchLang.Width; btnSearchLang.Height = ctrlSearchInput.Height; btnSearchLang.MouseClick += onSearchLang; // Update button texts; do it here so tooltip locations will be correct. simpTradChanged(); searchLangChanged(); // Lookup results control. ctrlResults = new ResultsControl(this, tprov, onLookupThroughLink, onGetEntry); ctrlResults.RelLocation = new Point(writingPad.RelRight + padding, ctrlSearchInput.RelBottom + padding); }
/// <summary> /// Converts a hybrid text to HTML (marking up hanzi+pinyin sections). /// </summary> public static string HybridToHtml(HybridText ht, SearchScript script) { StringBuilder sb = new StringBuilder(); bool first = true; for (int i = 0; i != ht.RunCount; ++i) { TextRun tr = ht.GetRunAt(i); if (tr is TextRunLatin) { string strRun = tr.GetPlainText(); if (!first && strRun != string.Empty && !char.IsPunctuation(strRun[0])) { sb.Append(' '); } sb.Append(strRun); } else { if (!first) { sb.Append(' '); } TextRunZho trz = tr as TextRunZho; string hanzi1 = (script == SearchScript.Traditional) ? trz.Trad : trz.Simp; if (string.IsNullOrEmpty(hanzi1)) { hanzi1 = null; } string hanzi2 = null; if (hanzi1 != null && script == SearchScript.Both && !string.IsNullOrEmpty(trz.Trad)) { hanzi2 = trz.Trad; } if (hanzi1 != null) { hanzi1 = escape(hanzi1); } if (hanzi2 != null) { hanzi2 = escape(hanzi2); } if (hanzi1 != null || hanzi2 != null) { sb.Append(templateSenseHanziOpen); } if (hanzi1 != null) { sb.Append(hanzi1); } if (hanzi2 != null) { sb.Append(' '); sb.Append(templateBullet); sb.Append(' '); sb.Append(hanzi2); } if (hanzi1 != null || hanzi2 != null) { sb.Append(templateSenseHanziClose); } if (trz.Pinyin != null) { if (hanzi1 != null) { sb.Append(' '); } sb.Append('['); sb.Append(escape(trz.GetPinyinInOne(true))); sb.Append(']'); } } first = false; } return(sb.ToString()); }
public LookupItem(int id, string text, SearchScript script, SearchLang lang) { ID = id; Text = text; Script = script; Lang = lang; }
/// <summary> /// Gets the HTML for a single sense, not including enclosing paragraph etc., only inline markup. /// </summary> /// <param name="tprov">Text provider if meta-labels (e.g. "Classifier") are to be included. If null, they are stripped.</param> private static string getSenseHtmlPure(ITextProvider tprov, CedictSense sense, SearchScript script) { StringBuilder sb = new StringBuilder(); string strDomain = HybridToHtml(sense.Domain, script); string strEquiv = HybridToHtml(sense.Equiv, script); string strNote = HybridToHtml(sense.Note, script); if (sense.Domain != HybridText.Empty) { if (sense.Domain.EqualsPlainText("CL:")) { if (tprov != null) { sb.Append(templateItalicsOpen); sb.Append(escape(tprov.GetString("ResultCtrlClassifier")) + " "); sb.Append(templateItalicsClose); } } else { sb.Append(templateItalicsOpen); sb.Append(strDomain); sb.Append(templateItalicsClose); } } if (sense.Domain != HybridText.Empty && !sense.Domain.EqualsPlainText("CL:")) { if (sense.Equiv != HybridText.Empty || sense.Note != HybridText.Empty) { sb.Append(' '); } } sb.Append(strEquiv); if (sense.Equiv != HybridText.Empty && sense.Note != HybridText.Empty) { sb.Append(' '); } if (sense.Note != HybridText.Empty) { sb.Append(templateItalicsOpen); sb.Append(strNote); sb.Append(templateItalicsClose); } // Done return(sb.ToString()); }
/// <summary> /// See <see cref="SetResults"/>. /// </summary> private bool doSetResults(int lookupId, ICedictEntryProvider entryProvider, ReadOnlyCollection<CedictResult> results, SearchScript script) { lock (displayIdLO) { // If we're already too late, don't bother changing display. if (displayId > lookupId) return false; displayId = lookupId; // Empty result set - special handling if (results.Count == 0) { lock (resCtrlsLO) { doDisposeResultControls(); txtResCount = tprov.GetString("ResultsCountNone"); setScrollbarVisibility(false); } // Render doFade(false); MakeMePaint(false, RenderMode.Invalidate); return true; } } // Decide if we first try with scrollbar visible or not // This is a very rough heuristics (10 results or more), but doesn't matter // Recalc costs much if there are many results, and the number covers that safely bool sbarVisible = results.Count > 10; // Content rectangle height and width int cw, ch; getContentSize(sbarVisible, out cw, out ch); // Create new result controls. At this point, not overwriting old ones! // This is the cycle that takes *long*. List<OneResultControl> newCtrls = new List<OneResultControl>(results.Count); int y = 0; using (Bitmap bmp = new Bitmap(1, 1)) using (Graphics g = Graphics.FromImage(bmp)) { bool canceled = false; for (int rix = 0; rix != results.Count; ++rix) { CedictResult cr = results[rix]; OneResultControl orc = new OneResultControl(null, Scale, tprov, onLookupFromCtrl, onPaintFromCtrl, onGetEntry, entryProvider, cr, script, rix == results.Count - 1); orc.Analyze(g, cw); // Cannot use RelLocation b/c control has no parent yet orc.AbsLocation = new Point(AbsLeft + 1, AbsTop + y + 1); y += orc.Height; newCtrls.Add(orc); // At any point, if we realize lookup ID has changed, we stop // This can happen if a later, quick lookup completes and shows results before us // Checking integers is atomic, no locking if (displayId > lookupId) { canceled = true; break; } } if (canceled) { foreach (OneResultControl orc in newCtrls) orc.Dispose(); return false; } } // OK, last chance to change our mind about showing results. // The rest is synchronized - but it's also fast lock (displayIdLO) { if (displayId > lookupId) return false; displayId = lookupId; // Rest must be invoked on GUI. Otherwise, as we're adding children, // Collections are modified that are also accessed by paint in a resize event handler etc. InvokeOnForm((MethodInvoker)delegate { // Stop any scrolling that may be going on. Cannot scroll what's being replaced. if (sbar.Parent == this) sbar.StopAnyScrolling(); // Prevent any painting from worker threads - also accesses collection we're changing lock (resCtrlsLO) { // Get rid of old result controls, remember/own new ones doDisposeResultControls(); resCtrls = newCtrls; foreach (OneResultControl orc in resCtrls) AddChild(orc); // Actually show or hide scrollbar as per original decision setScrollbarVisibility(sbarVisible); // Now, by the time we're here, size may have changed // That is unlikely, but then we got to re-layout stuff int cwNew, chNew; getContentSize(sbarVisible, out cwNew, out chNew); if (cwNew != cw || chNew != ch) reAnalyzeResultsDisplay(); else { // Everything as big as it used to be... // Change our mind about scrollbar? cw = showOrHideScrollbar(); } } // Results count text if (resCtrls.Count == 1) txtResCount = tprov.GetString("ResultsCountOne"); else { txtResCount = tprov.GetString("ResultsCountN"); txtResCount = string.Format(txtResCount, resCtrls.Count); } // Update first visible control's index updateFirstVisibleIdx(); // Render doFade(false); MakeMePaint(false, RenderMode.Invalidate); }); // Done. return true; } }
/// <summary> /// Retrieves entries for a Chinese search expression (pinyin vs. hanzi auto-detected) /// </summary> private List<CedictResult> doChineseLookup(BinReader br, string query, SearchScript script) { List<CedictResult> res = new List<CedictResult>(); // If query string has ideographic characters, do hanzi looup if (hasIdeo(query)) res = doHanziLookupHead(br, query, script); // Otherwise, do pinyin lookup else { // Parse pinyin query string List<PinyinSyllable> sylls = doParsePinyin(query); // Lookup res = doPinyinLookupHead(br, sylls); } // Done return res; }