How do i subscribe to onscroll event in Blazor? - header

I am trying to react to the onscroll event in Blazor to animate an image when the user scrolls down the web page (something like the brand logo on this website: https://lebenswelten-stgabriel.at/). I have tried the native onscroll events and also tried to use the js interop but it doesn't do anything. Is it something that is currently unavailable in Blazor or could I just be listening to the scrolling event on the wrong component?

After a couple of trials and errors, I decided to create a custom .net service that I register over on the js side. The service looks a something like this:
using System;
using Microsoft.JSInterop;
namespace myBlazorApp.Services
{
public class ScrollInfoService: IScrollInfoService
{
public event EventHandler<int> OnScroll;
public ScrollInfoService(IJSRuntime jsRuntime)
{
RegisterServiceViaJsRuntime(jsRuntime);
}
private void RegisterServiceViaJsRuntime(IJSRuntime jsRuntime)
{
jsRuntime.InvokeVoidAsync("RegisterScrollInfoService", DotNetObjectReference.Create(this));
}
public int YValue { get; private set; }
[JSInvokable("OnScroll")]
public void JsOnScroll(int yValue)
{
YValue = yValue;
Console.WriteLine("ScrollInfoService.OnScroll " + yValue);
OnScroll?.Invoke(this, yValue);
}
}
public interface IScrollInfoService
{
event EventHandler<int> OnScroll;
int YValue { get; }
}
}
The service receives the onscroll event from the DOM and raises an event on the .net side. Here is what the script looks like on the js side:
window.onscroll = function() {
if (window.scrollInfoService != null)
window.scrollInfoService.invokeMethodAsync('OnScroll', window.pageYOffset);
}
window.RegisterScrollInfoService = (scrollInfoService) => {
window.scrollInfoService = scrollInfoService;
}
I then inject my service in any Blazor component that needs it like this:
#using myBlazorApp.Services
#inject IScrollInfoService scrollInfoService
#implements IDisposable
<div>
...
</div>
#code {
protected override void OnInitialized()
{
scrollInfoService.OnScroll += OnScroll;
}
private void OnScroll(object sender, int yValue)
{
DoSomething(yValue);
}
public void Dispose()
{
scrollInfoService.OnScroll -= OnScroll;
}
}

This is how to get scrollTop for myDiv div via eval (weird but working) - see it in action on Blazor REPL.
#inject IJSRuntime JSRuntime;
<h1>DIV scrollTop: #ScrollTop</h1>
<div id="myDiv" style="overflow:scroll; height:400px;" #onscroll="#OnScroll" >
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
</div>
#code {
public int ScrollTop { get; set; }
private async Task OnScroll(EventArgs e)
{
ScrollTop = await JSRuntime.InvokeAsync<int>(
"eval", "document.getElementById('myDiv').scrollTop");
}
}
UPDATE: the similar way to do that but without using eval - thanks to Kristian Mariyanov. See it in action on Blazor REPL here - here you need to add JS custom function getScrollToTop to DOM window object:
<script>
window.getScrollToTop = (selector) => {
return document.querySelector(selector).scrollTop
}
</script>
and invoke it this way:
ScrollTop = await JS.InvokeAsync<int>("getScrollToTop", "#myDiv");

You can easily handle the Blazor OnScroll event by simply creating a Razor page that works as follows:
#page "/ScrollTest"
<h3>Scrolling</h3>
<div class="scroll" #onscroll="OnScroll">
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
fdsgdf qsdfsqdf sqfgdsfgdfg rtg ret rez
</div>
<div class="m-2 p-2">Scroll events: #counter</div>
<style>
div.scroll {
margin: 4px, 4px;
padding: 4px;
width: 500px;
height: 110px;
overflow-x: hidden;
overflow-y: auto;
text-align: justify;
}
</style>
#code {
private int counter;
private void OnScroll()
{
counter++;
}
}

Related

How to decompress/deflate PDF Stream

Working with the 2016-W4 pdf, which has 2 large streams (page 1 & 2), along with a bunch of other objects and smaller streams. I'm trying to deflate the stream(s), to work with the source data, but am struggling. I'm only able to get corrupt inputs and invalid checksums errors.
I've written a test script to help debug, and have pulled out smaller streams from the file to test with.
Here are 2 streams from the original pdf, along with their length objects:
stream 1:
149 0 obj
<< /Length 150 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType
1 /BBox [0 0 8 8] /Resources 151 0 R >>
stream
x+TT(T0�B ,JUWÈS0Ð37±402V(NFJS�þ¶
«
endstream
endobj
150 0 obj
42
endobj
stream 2
142 0 obj
<< /Length 143 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType
1 /BBox [0 0 0 0] /Resources 144 0 R >>
stream
x+T�ç�ã
endstream
endobj
143 0 obj
11
endobj
I copied just the stream contents into new files within Vim (excluding the carriage returns after stream and before endstream).
I've tried both:
compress/flate (rfc-1951) – (removing the first 2 bytes (CMF, FLG))
compress/zlib (rfc-1950)
I've converted the streams to []byte for the below:
package main
import (
"bytes"
"compress/flate"
"compress/gzip"
"compress/zlib"
"fmt"
"io"
"os"
)
var (
flateReaderFn = func(r io.Reader) (io.ReadCloser, error) { return flate.NewReader(r), nil }
zlibReaderFn = func(r io.Reader) (io.ReadCloser, error) { return zlib.NewReader(r) }
)
func deflate(b []byte, skip, length int, newReader func(io.Reader) (io.ReadCloser, error)) {
// rfc-1950
// --------
// First 2 bytes
// [120, 1] - CMF, FLG
//
// CMF: 120
// 0111 1000
// ↑ ↑
// | CM(8) = deflate compression method
// CINFO(7) = 32k LZ77 window size
//
// FLG: 1
// 0001 ← FCHECK
// (CMF*256 + FLG) % 31 == 0
// 120 * 256 + 1 = 30721
// 30721 % 31 == 0
stream := bytes.NewReader(b[skip:length])
r, err := newReader(stream)
if err != nil {
fmt.Println("\nfailed to create reader,", err)
return
}
n, err := io.Copy(os.Stdout, r)
if err != nil {
if n > 0 {
fmt.Print("\n")
}
fmt.Println("\nfailed to write contents from reader,", err)
return
}
fmt.Printf("%d bytes written\n", n)
r.Close()
}
func main() {
//readerFn, skip := flateReaderFn, 2 // compress/flate RFC-1951, ignore first 2 bytes
readerFn, skip := zlibReaderFn, 0 // compress/zlib RFC-1950, ignore nothing
// ⤹ This is where the error occurs: `flate: corrupt input before offset 19`.
stream1 := []byte{120, 1, 43, 84, 8, 84, 40, 84, 48, 0, 66, 11, 32, 44, 74, 85, 8, 87, 195, 136, 83, 48, 195, 144, 51, 55, 194, 177, 52, 48, 50, 86, 40, 78, 70, 194, 150, 74, 83, 8, 4, 0, 195, 190, 194, 182, 10, 194, 171, 10}
stream2 := []byte{120, 1, 43, 84, 8, 4, 0, 1, 195, 167, 0, 195, 163, 10}
fmt.Println("----------------------------------------\nStream 1:")
deflate(stream1, skip, 42, readerFn) // flate: corrupt input before offset 19
fmt.Println("----------------------------------------\nStream 2:")
deflate(stream2, skip, 11, readerFn) // invalid checksum
}
I'm sure I'm doing something wrong somewhere, I just can't quite see it.
(The pdf does open in a viewer)
Binary data should never be copied out of / saved from text editors. There might be cases when this succeeds, and it just adds oil to the flame.
Your data that you eventually "mined out" from the PDF is most likely not identical to the actual data that is in the PDF. You should take the data from a hex editor (e.g. try hecate for something new), or write a simple app that saves it (which strictly handles the file as binary).
Hint #1:
The binary data displayed spread across multiple lines. Binary data does not contain carriage returns, that's a textual control. If it does, that means the editor did interpret it as text, and so some codes / characters where "consumed" to start a new line. Multiple sequences may be interpreted as the same newline (e.g. \n, \r\n). By excluding them, you're already at data loss, by including them, you might already have a different sequence. And if the data was interpreted and displayed as text, more problems may arise as there are more control characters, and some characters may not appear when displayed.
Hint #2:
When flateReaderFn is used, decoding the 2nd example succeeds (completes without an error). This means "you were barking up the right tree", but the success depends on what the actual data is and to what extent was it "distorted" by the text editor.
Okay, confession time...
I was so caught up in trying to understand deflate that I completely overlooked the fact that Vim wasn't saving the stream contents correctly into new files. So I spent quite a bit of time reading the RFC's, and digging through the internals of the Go compress/... packages, assuming the problem was with my code.
Shortly after I posted my question I tried reading the PDF as a whole, finding the stream/endstream locations, and pushing that through deflate. As soon as I saw the content scroll through the screen I realized my dumb mistake.
+1 #icza, that was exactly my issue.
It was good in then end, as I have a much better understanding of the whole process than if it would have just worked the first go around.
Extracting objects from PDF can be tricky depending on the filters used. The filter can also have additional options which need to be handled correctly.
For someone interested in extracting an object without taking care of the low-level details of the process.
To get a single object from a PDF and decode it can be done as follows:
package main
import (
"fmt"
"os"
"strconv"
"github.com/unidoc/unipdf/v3/core"
"github.com/unidoc/unipdf/v3/model"
)
func main() {
objNum := 149 // Get object 149
err := inspectPdfObject("input.pdf", objNum)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
}
func inspectPdfObject(inputPath string, objNum int) error {
f, err := os.Open(inputPath)
if err != nil {
return err
}
defer f.Close()
pdfReader, err := model.NewPdfReader(f)
if err != nil {
return err
}
isEncrypted, err := pdfReader.IsEncrypted()
if err != nil {
return err
}
if isEncrypted {
// If encrypted, try decrypting with an empty one.
// Can also specify a user/owner password here by modifying the line below.
auth, err := pdfReader.Decrypt([]byte(""))
if err != nil {
fmt.Printf("Decryption error: %v\n", err)
return err
}
if !auth {
fmt.Println(" This file is encrypted with opening password. Modify the code to specify the password.")
return nil
}
}
obj, err := pdfReader.GetIndirectObjectByNumber(objNum)
if err != nil {
return err
}
fmt.Printf("Object %d: %s\n", objNum, obj.String())
if stream, is := obj.(*core.PdfObjectStream); is {
decoded, err := core.DecodeStream(stream)
if err != nil {
return err
}
fmt.Printf("Decoded:\n%s", decoded)
} else if indObj, is := obj.(*core.PdfIndirectObject); is {
fmt.Printf("%T\n", indObj.PdfObject)
fmt.Printf("%s\n", indObj.PdfObject.String())
}
return nil
}
A full example: pdf_get_object.go
Disclosure: I am the original developer of UniPDF.

Canon EDSDK Device Busy when changing property

I have a little problem with EDSDK.
I tried to make a function that can switch from liveview to movie record.
Everything is fine with liveview, I can take picture.
But when I want to start movie record, I have a ERR_DEVICE_BUSY everytime I want to change a property (save to camera instead of PC). I try everything, make a loop, nothing. And it's the only place where I get this error.
Here's my code :
EdsDataType dataType;
EdsUInt32 dataSize;
EdsUInt32 enregistrement;
err = EdsGetPropertySize(camera, kEdsPropID_SaveTo, 0, &dataType, &dataSize);
err = EdsGetPropertyData(camera, kEdsPropID_SaveTo, 0, dataSize, &enregistrement);
EdsUInt32 saveTo = kEdsSaveTo_Camera;
if(enregistrement != kEdsSaveTo_Camera){
err = EdsSetPropertyData(camera, kEdsPropID_SaveTo, 0, sizeof(saveTo), &saveTo);
if(err != EDS_ERR_OK){
printf("Erreur de changement d'emplacement de sauvegarde, arret de l'enregistrement\n");
printf("err : 0x%X\n", err);
return err;
}
}
//Changement du mode de prise de vu
EdsUInt32 mode = 20; //Le monde 20 correspont à l'enregistrement vidéo
EdsSetPropertyData(camera, kEdsPropID_AEMode, 0, sizeof(mode), &mode);
//Variable pour la mise en route et l'arrêt de l'enregistrement
EdsUInt32 debutEnregistrement = 4;
EdsUInt32 finEnregistrement = 0;
err = EdsSetPropertyData(camera, kEdsPropID_Record, 0, sizeof(debutEnregistrement), &debutEnregistrement);
if(err != EDS_ERR_OK){
printf("Erreur lors du lancement de l'enregistrement");
return err;
}
//Wait for stop record
err = EdsSetPropertyData(camera, kEdsPropID_Record, 0, sizeof(finEnregistrement), &finEnregistrement);
if(err != EDS_ERR_OK)
printf("Erreur lors de l'arret de l'enregistrement");
//L'enregistrement est fini, vérification des evenement
EdsGetEvent();
If you have solution, I take, thanks.
Regardless of what the docs say, the EDSDK sometimes returns EDS_ERR_DEVICE_BUSY for EdsSetPropertyData() when the actual error is a bad input parameter. For example, try setting kEdsPropID_Av with a value of decimal 50 (0x32) which is not in the table given in the documentation. On my EOS 5Ds, this returns EDS_ERR_DEVICE_BUSY no matter how many retries are attempted. Passing a legal value, e.g. 0x33 (for f/6.3) succeeds first time. The bug is 100% reproducible here.
So, if you get this "device busy" error when setting properties, check the input values you are passing with a fine-toothed comb.
You can try this I don't know if you already but it should work it has for me so far on all my property changes.
Just after you opened the session use the EdsSetPropertyEventHandler function
Begin by stopping you LiveView by putting kEdsEvfOutputDevice at TFT if I recall good. (the parameter that is not PC).
In the call back of the property event handler you make a switch on the event and when it is for the property kEdsEvfOutputDevice, then you throw your function to go to movie mode.
the callback function will be called when the property changed will be made so you won't have any device busy or notready. But becareful you have to set your callback function to static so that it works. So you wont be able to call anyother functions that the one that are static, the same for the variables.
If you didn't understand I can explain it to you in french, far more easy for me ^^

Wrong number of characters in line returned by CTLineGetStringRange and CTLineGetGlyphCount

I have an app where I use CoreText to draw text highlight. It works well except that when I try to get the number of characters in a line by using CTLineGetStringRange it usually gives me a number larger then it actually is. For instance in a line containing 156 characters the length of the range was 164. CTLineGetGlyphCountreturned the same number.
Does anybody have an idea why this happens? The NSAttributedStringI use to create the framesetter is using the exact same font as my UITextView.
Here is my code:
// Build the attributed string from our text data and string attribute data
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:self.text attributes:self.attributes];
// Create the Core Text framesetter using the attributed string
_framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attributedString);
// Create the Core Text frame using our current view rect bounds
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
_frame = CTFramesetterCreateFrame(_framesetter, CFRangeMake(0, 0), path.CGPath, NULL);
NSArray *lines = (__bridge NSArray *) CTFrameGetLines(_frame);
for (int i = 0; i < lines.count; i++) {
CTLineRef line = (__bridge CTLineRef) [lines objectAtIndex:i];
CFRange lineRange = CTLineGetStringRange(line);
NSLog(#"lineRange: %ld, %ld", lineRange.location, lineRange.length);
CFIndex glyphCount = CTLineGetGlyphCount(line);
NSLog(#"glyphCount: %ld", glyphCount);
}
My class is a subclass of UIView which is added as subview to an instance of a subclass of UITextView.
EDIT:
Here is an example string I am testin with:
textView.text = #"Marvel's The Avengers is a 2012 American superhero film produced by Marvel Studios and distributed by Walt Disney Pictures, based on the Marvel Comics superhero team of the same name.";
In this case the first line contains
"Marvel's The Avengers is a 2012 American superhero film produced by Marvel Studios and distributed by Walt Disney Pictures, based on the " which is 137 characters long. But it gives me a range for the line with length 144.
However, when I tried with the following text the results were different:
textView.text = #"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
Now the first line contains
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " with length 142. But here it gives me the correct range with length 142.
I then tried with several more texts:
Text:
"The Asgardian Loki encounters the Other, the leader of an alien race known as the Chitauri. In exchange for retrieving the Tesseract, a powerful energy source of unknown potential, the Other promises Loki a Chitauri army with which he can subjugate the Earth."
Result: "The Asgardian Loki encounters the Other, the leader of an alien race known as the Chitauri. In exchange for retrieving the Tesseract, a powerful " with length 145.
Line range length: 145
Text:
"Stark and Rogers realize that simply defeating them will not be enough for Loki; he needs to overpower them publicly to validate himself as ruler of Earth."
Result: "Stark and Rogers realize that simply defeating them will not be enough for Loki; he needs to overpower them publicly to validate himself as ruler " with length 146.
Line range length: 149
So as you can see sometimes it is correct and sometimes it is not. I can't find the explanation.
I solved the problem based on the hint given in this answer.
It seems that because UITextViewinherits from UIScrollView it has an 8 pixel inset at every edge. This meant that my internal UIViewhad an 16 pixel wider room for text then my UITextView and sometimes that difference meant that it could fit one more word before going to a new line, which gave the wrong number of characters in the line.
So subtracting 16 pixels from the width of the view solved the issue for me.
However, this was only part of my solution. The other part was to remove kerning and ligatures from my attributed string in core text:
CFAttributedStringSetAttribute(string, textRange, kCTKernAttributeName, (__bridge CFTypeRef)([NSNumber numberWithFloat:0.0]));
CFAttributedStringSetAttribute(string, textRange, kCTLigatureAttributeName, (__bridge CFTypeRef)([NSNumber numberWithInt:0]));
Now the lines fit perfectly.

IOleInPlaceSiteWindowless::AdjustRect not working?

I am using IOleInPlaceSiteWindowless::AdjustRect to properly capture and release the mouse in a windowless ActiveX control hosted in IE:
LRESULT CD3DControl::OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
CRect rc(CPoint(lParam), CSize(0, 0));
HRESULT hr = m_spInPlaceSite->AdjustRect(rc);
bool isInside = hr == S_OK;
TRACE("AdjustRect 0x%X, isInside=%d %d %d %d %d\n",
hr, isInside, rc.top, rc.left, rc.bottom, rc.right);
if (m_spInPlaceSite->GetCapture() == S_FALSE)
{
if (isInside)
{
hr = m_spInPlaceSite->SetCapture(TRUE);
TRACE("SetCapture(TRUE) 0x%X\n", hr);
}
}
else if (!isInside)
{
hr = m_spInPlaceSite->SetCapture(FALSE);
TRACE("SetCapture(FALSE) 0x%X\n", hr);
}
return 0;
}
When the mouse enters my control's rect things work great and the control captures the mouse. However, when my mouse leaves the control's area, AdjustRect still returns S_OK. It also returns S_OK if the mouse hovers over a div that covers part of my control.
These results are not consistent with the AdjustRect documentation.
To debug this further, I re-wrote OnMouseMove:
LRESULT CD3DControl::OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
CRect rc(0, 0, 2000, 2000);
HRESULT hr = m_spInPlaceSite->AdjustRect(&rc);
bool isInside = hr == S_OK;
TRACE("AdjustRect 0x%X, isInside=%d %d %d %d %d\n",
hr, isInside, rc.top, rc.left, rc.bottom, rc.right);
return 0;
}
In this case, AdjustRect also returns S_OK, but the rectangle isn't adjusted at all! It is still (0,0)x(2000,2000).
For OnMouseOut on windowless controls I'm usually using TrackMouseEvent on the container hwnd and monitor WM_MOUSELEAVE and WM_MOUSEMOVE.
Also, keep in mind when authoring windowless controls that some containers refuse windowless instantiation so your controls turn into full blown "windowful" controls there. Most notably MS Access is such a beast. In this case you never get a call on IOleInPlaceObjectWindowless::OnWindowMessage because you have you own hwnd.
The amazing Igor Tandetnik answered my question here:
http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/b8586255-0321-450e-9c8a-090e47ce13c4/
Evidently the function simply isn't implemented. IE should return E_NOTIMPLEMENTED
-Erik

How do I lex this input?

I currently have a working, simple language implemented in Java using ANTLR. What I want to do is embed it in plain text, in a similar fashion to PHP.
For example:
Lorem ipsum dolor sit amet
<% print('consectetur adipiscing elit'); %>
Phasellus volutpat dignissim sapien.
I anticipate that the resulting token stream would look something like:
CDATA OPEN PRINT OPAREN APOS STRING APOS CPAREN SEMI CLOSE CDATA
How can I achieve this, or is there a better way?
There is no restriction on what might be outside the <% block. I assumed something like <% print('%>'); %>, as per Michael Mrozek's answer, would be possible, but outside of a situation like that, <% would always indicate the start of a code block.
Sample Implementation
I developed a solution based on ideas given in Michael Mrozek's answer, simulating Flex's start conditions using ANTLR's gated semantic predicates:
lexer grammar Lexer;
#members {
boolean codeMode = false;
}
OPEN : {!codeMode}?=> '<%' { codeMode = true; } ;
CLOSE : {codeMode}?=> '%>' { codeMode = false;} ;
LPAREN : {codeMode}?=> '(';
//etc.
CHAR : {!codeMode}?=> ~('<%');
parser grammar Parser;
options {
tokenVocab = Lexer;
output = AST;
}
tokens {
VERBATIM;
}
program :
(code | verbatim)+
;
code :
OPEN statement+ CLOSE -> statement+
;
verbatim :
CHAR -> ^(VERBATIM CHAR)
;
but outside of a situation like that, <% would always indicate the start of a code block.
In that case, first scan the file for your embedded code, and once you have those, parse your embedded code with a dedicated parser (without the noise before the <% and after the %> tags).
ANTLR has the option to let the lexer parse just a (small) part of an input file and ignore the rest. Note that you cannot create a "combined grammar" (parser and lexer in one) in that case. Here's how you can create such a "partial lexer":
// file EmbeddedCodeLexer.g
lexer grammar EmbeddedCodeLexer;
options{filter=true;} // <- enables the partial lexing!
EmbeddedCode
: '<%' // match an open tag
( String // ( match a string literal
| ~('%' | '\'') // OR match any char except `%` and `'`
| {input.LT(2) != '>'}?=> '%' // OR only match a `%` if `>` is not ahead of it
)* // ) <- zero or more times
'%>' // match a close tag
;
fragment
String
: '\'' ('\\' . | ~('\'' | '\\'))* '\''
;
If you now create a lexer from it:
java -cp antlr-3.2.jar org.antlr.Tool EmbeddedCodeLexer.g
and create a little test harness:
import org.antlr.runtime.*;
public class Main {
public static void main(String[] args) throws Exception {
String source = "Lorem ipsum dolor sit amet \n"+
"<% \n"+
"a = 2 > 1 && 10 % 3; \n"+
"print('consectetur %> adipiscing elit'); \n"+
"%> \n"+
"Phasellus volutpat dignissim sapien. \n"+
"foo <% more code! %> bar \n";
ANTLRStringStream in = new ANTLRStringStream(source);
EmbeddedCodeLexer lexer = new EmbeddedCodeLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
for(Object o : tokens.getTokens()) {
System.out.println("=======================================\n"+
"EmbeddedCode = "+((Token)o).getText());
}
}
}
compile it all:
javac -cp antlr-3.2.jar *.java
and finally run the Main class by doing:
// *nix/MacOS
java -cp .:antlr-3.2.jar Main
// Windows
java -cp .;antlr-3.2.jar Main
it will produce the following output:
=======================================
EmbeddedCode = <%
a = 2 > 1 && 10 % 3;
print('consectetur %> adipiscing elit');
%>
=======================================
EmbeddedCode = <% more code! %>
The actual concept looks fine, although it's unlikely you'd have a PRINT token; the lexer would probably emit something like IDENTIFIER, and the parser would be responsible for figuring out that it's a function call (e.g. by looking for IDENTIFIER OPAREN ... CPAREN) and doing the appropriate thing.
As for how to do it, I don't know anything about ANTLR, but it probably has something like flex's start conditions. If so, you can have the INITIAL start condition do nothing but look for <%, which would switch to the CODE state where all the actual tokens are defined; then '%>' would switch back. In flex it would be:
%s CODE
%%
<INITIAL>{
"<%" {BEGIN(CODE);}
. {}
}
/* All these are implicitly in CODE because it was declared %s,
but you could wrap it in <CODE>{} too
*/
"%>" {BEGIN(INITIAL);}
"(" {return OPAREN;}
"'" {return APOS;}
...
You need to be careful about things like matching %> in a context where it's not a closing marker, like within a string; it's up to you if you want to allow <% print('%>'); %>, but most likely you do