/// <summary> /// Paints control. /// </summary> /// <remarks> /// This paint function is non-standard in that it does not invoke base.DoPaint. /// Base paint would take care of children, BUT: we don't want that: we only paint visible results controls. /// In exchange, we must manually paint or one real child, the scroll bar. /// </remarks> public override void DoPaint(Graphics g) { // Content rectangle height and width int cw, ch; getContentSize(sbar.Parent == this, out cw, out ch); // Background using (Brush b = new SolidBrush(ZenParams.WindowColor)) { g.FillRectangle(b, new Rectangle(0, 0, Width, Height)); } // Border using (Pen p = new Pen(ZenParams.BorderColor)) { g.DrawLine(p, 0, 0, Width, 0); g.DrawLine(p, Width - 1, 0, Width - 1, Height); g.DrawLine(p, Width - 1, Height - 1, 0, Height - 1); g.DrawLine(p, 0, Height - 1, 0, 0); } // Results g.ResetTransform(); g.TranslateTransform(AbsLeft, AbsTop); g.Clip = new Region(new Rectangle(1, 1, cw, ch)); lock (resCtrlsLO) { if (firstVisibleIdx != -1) { int ix = firstVisibleIdx; while (ix < resCtrls.Count) { OneResultControl orc = resCtrls[ix]; if (orc.RelTop >= ch + 1) { break; } g.ResetTransform(); g.TranslateTransform(orc.AbsLeft, orc.AbsTop); orc.DoPaint(g); ++ix; } } } // Bottom overlay (results count, zoom, settings) g.ResetTransform(); g.TranslateTransform(AbsLeft, AbsTop); g.Clip = new Region(new Rectangle(1, 1, cw, ch)); doPaintBottomOverlay(g); // Scroll bar, if visible if (sbar.Parent == this) { g.ResetTransform(); g.TranslateTransform(sbar.AbsLeft, sbar.AbsTop); g.Clip = new Region(new Rectangle(0, 0, sbar.Width, sbar.Height)); sbar.DoPaint(g); } // Fade overaly above everything but scollbar Color colFade = getFadeColor(); if (colFade.A != 0) { using (Brush b = new SolidBrush(colFade)) { g.ResetTransform(); g.TranslateTransform(AbsLeft, AbsTop); Rectangle rect = new Rectangle(1, 1, cw, ch); g.Clip = new Region(rect); g.FillRectangle(b, rect); } } }
private void reAnalyzeResultsDisplay() { // Content rectangle height and width int cw, ch; getContentSize(sbar.Parent == this, out cw, out ch); // Put scroll bar in its place, update its large change (which is always one full screen) // Call below will not change visibility, but update sbar position. setScrollbarVisibility(sbar.Parent == this); // No results being shown: done here if (resCtrls.Count == 0) { return; } // Pivot control: first one that's at least partially visible int pivotY = 1; int pivotIX = -1; OneResultControl pivotCtrl = null; for (int i = 0; i != resCtrls.Count; ++i) { OneResultControl orc = resCtrls[i]; // Results controls' absolute locations are within my full canvas // First visible one is the guy whose bottom is greater than 1 if (orc.RelBottom > pivotY) { pivotY = orc.RelBottom; pivotCtrl = orc; pivotIX = i; break; } } // Recalculate each result control's layout, and height using (Bitmap bmp = new Bitmap(1, 1)) using (Graphics g = Graphics.FromImage(bmp)) { foreach (OneResultControl orc in resCtrls) { orc.Analyze(g, cw); } } // Move pivot control back in place so bottom stays where it was // But: if pivot was first shown control at top, keep top in place int diff = pivotY - pivotCtrl.RelBottom; if (pivotIX == 0 && pivotCtrl.RelTop == 1) { diff = 0; } pivotCtrl.RelTop += diff; // Lay out remaining controls up and down for (int i = pivotIX + 1; i < resCtrls.Count; ++i) { resCtrls[i].RelTop = resCtrls[i - 1].RelBottom; } for (int i = pivotIX - 1; i >= 0; --i) { resCtrls[i].RelTop = resCtrls[i + 1].RelTop - resCtrls[i].Height; } // Edge case: very first control's top must not be greater than 1 if (resCtrls[0].RelTop > 1) { diff = resCtrls[0].RelTop - 1; foreach (OneResultControl orc in resCtrls) { orc.RelTop -= diff; } } // If there is space below very last control's bottom, but first control is above window edge // > Move down, but without detaching creating empty space at top int emptyAtBottom = Height - 1 - resCtrls[resCtrls.Count - 1].RelBottom; if (emptyAtBottom > 0) { int outsideAtTop = 1 - resCtrls[0].RelTop; diff = Math.Min(outsideAtTop, emptyAtBottom); if (diff > 0) { foreach (OneResultControl orc in resCtrls) { orc.RelTop += diff; } } } // Change our mind about scrollbar control? cw = showOrHideScrollbar(); // Update first visible control's index updateFirstVisibleIdx(); }
/// <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> /// 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; } }