#![cfg(windows)]
use std::cell::Cell;
use std::mem::ManuallyDrop;
use std::rc::Rc;
use denise_activex::DenisePanel;
use windows::Win32::Foundation::{DV_E_DVASPECT, RECTL};
use windows::Win32::Graphics::Gdi::{
BI_RGB, BITMAPINFO, BITMAPINFOHEADER, CreateCompatibleDC, CreateDIBSection, DIB_RGB_COLORS,
DeleteDC, DeleteObject, GdiFlush, HBITMAP, HDC, HGDIOBJ, SelectObject,
};
use windows::Win32::System::Com::{
ADVF_ONLYONCE, DISPATCH_PROPERTYPUT, DISPPARAMS, DVASPECT_CONTENT, DVASPECT_THUMBNAIL,
FORMATETC, IAdviseSink, IAdviseSink_Impl, IDispatch, IMoniker, STGMEDIUM,
};
use windows::Win32::System::Ole::{DISPID_PROPERTYPUT, IOleObject, IViewObject, IViewObject2};
use windows::Win32::System::Variant::{
VARENUM, VARIANT, VARIANT_0, VARIANT_0_0, VARIANT_0_0_0, VT_BSTR, VariantClear,
};
use windows_core::{BSTR, GUID, Interface, implement};
const WIDTH: i32 = 200;
const HEIGHT: i32 = 120;
const SENTINEL: u32 = 0x00FF_00FF;
fn panel() -> IViewObject2 {
DenisePanel::new().into()
}
#[test]
fn the_control_draws_itself_with_no_window_and_no_container() {
let canvas = Canvas::new(WIDTH, HEIGHT);
draw(&panel(), &canvas, bounds(0, 0, WIDTH, HEIGHT)).expect("Draw");
let pixels = canvas.pixels();
let untouched = pixels.iter().filter(|&&p| p == SENTINEL).count();
assert_eq!(untouched, 0, "the whole rectangle should have been covered");
let distinct = {
let mut seen = pixels.clone();
seen.sort_unstable();
seen.dedup();
seen.len()
};
assert!(
distinct > 4,
"only {distinct} colours: that is a background, not a panel"
);
}
#[test]
fn the_picture_follows_a_property_a_designer_assigned() {
let panel = panel();
let canvas = Canvas::new(WIDTH, HEIGHT);
draw(&panel, &canvas, bounds(0, 0, WIDTH, HEIGHT)).expect("Draw");
let before = canvas.pixels();
let dispatch: IDispatch = panel.cast().expect("IDispatch");
put_caption(&dispatch, "Sett i designeren");
draw(&panel, &canvas, bounds(0, 0, WIDTH, HEIGHT)).expect("Draw");
let after = canvas.pixels();
assert_ne!(
before, after,
"the heading changed and the picture did not: Draw is rendering a cached \
view rather than the model"
);
}
#[test]
fn an_aspect_this_control_has_no_picture_for_is_declined() {
let canvas = Canvas::new(WIDTH, HEIGHT);
let rect = bounds(0, 0, WIDTH, HEIGHT);
let result = unsafe {
panel().Draw(
DVASPECT_THUMBNAIL,
-1,
core::ptr::null_mut(),
None,
None,
canvas.dc,
Some(&rect),
None,
None,
0,
)
};
assert_eq!(
result.expect_err("a thumbnail").code(),
DV_E_DVASPECT,
"the documented refusal, which is what makes a container fall back"
);
}
#[test]
fn an_empty_rectangle_leaves_the_context_untouched() {
let canvas = Canvas::new(WIDTH, HEIGHT);
draw(&panel(), &canvas, bounds(40, 40, 40, 90)).expect("an empty rectangle is not an error");
assert!(
canvas.pixels().iter().all(|&p| p == SENTINEL),
"something was drawn for a rectangle with no area in it"
);
}
#[test]
fn null_bounds_are_refused_rather_than_dereferenced() {
let canvas = Canvas::new(WIDTH, HEIGHT);
let result = unsafe {
panel().Draw(
DVASPECT_CONTENT,
-1,
core::ptr::null_mut(),
None,
None,
canvas.dc,
None,
None,
None,
0,
)
};
assert!(result.is_err(), "a null rectangle must not be dereferenced");
}
#[test]
fn the_extent_agrees_with_the_one_ioleobject_reports() {
let panel = panel();
let ole: IOleObject = panel.cast().expect("IOleObject");
let (view, object) = unsafe {
(
panel
.GetExtent(DVASPECT_CONTENT, -1, core::ptr::null())
.expect("view"),
ole.GetExtent(DVASPECT_CONTENT).expect("object"),
)
};
assert_eq!((view.cx, view.cy), (object.cx, object.cy));
assert_eq!(
(view.cx, view.cy),
(
denise_activex::himetric::pixels_to_himetric(WIDTH),
denise_activex::himetric::pixels_to_himetric(HEIGHT)
)
);
}
#[test]
fn the_base_interface_is_reachable_from_the_derived_one() {
let base: IViewObject = panel().cast().expect("IViewObject from IViewObject2");
let derived: IViewObject2 = base.cast().expect("and back again");
let _ = unsafe { derived.GetExtent(DVASPECT_CONTENT, -1, core::ptr::null()) }.expect("extent");
}
#[test]
fn a_view_sink_is_told_when_a_property_changes_the_picture() {
let panel = panel();
let changes = Rc::new(Cell::new(0u32));
let sink: IAdviseSink = Watcher(changes.clone()).into();
unsafe { panel.SetAdvise(DVASPECT_CONTENT, 0, &sink) }.expect("SetAdvise");
let dispatch: IDispatch = panel.cast().expect("IDispatch");
put_caption(&dispatch, "Ny tittel");
assert_eq!(changes.get(), 1, "the sink was not told");
put_caption(&dispatch, "Enda en");
assert_eq!(changes.get(), 2, "a standing registration fires every time");
unsafe { panel.SetAdvise(DVASPECT_CONTENT, 0, None::<&IAdviseSink>) }.expect("SetAdvise(None)");
put_caption(&dispatch, "Etter avmelding");
assert_eq!(changes.get(), 2, "a withdrawn sink was still called");
}
#[test]
fn a_sink_registered_once_is_told_once() {
let panel = panel();
let changes = Rc::new(Cell::new(0u32));
let sink: IAdviseSink = Watcher(changes.clone()).into();
unsafe { panel.SetAdvise(DVASPECT_CONTENT, ADVF_ONLYONCE.0 as u32, &sink) }.expect("SetAdvise");
let dispatch: IDispatch = panel.cast().expect("IDispatch");
put_caption(&dispatch, "Første");
put_caption(&dispatch, "Andre");
assert_eq!(changes.get(), 1, "ONLYONCE means once");
}
#[test]
fn the_registration_reads_back_and_tolerates_the_parts_not_asked_for() {
let panel = panel();
let sink: IAdviseSink = Watcher(Rc::new(Cell::new(0))).into();
unsafe { panel.SetAdvise(DVASPECT_CONTENT, ADVF_ONLYONCE.0 as u32, &sink) }.expect("SetAdvise");
let mut aspects = 0u32;
let mut advf = 0u32;
let mut back: Option<IAdviseSink> = None;
unsafe { panel.GetAdvise(Some(&mut aspects), Some(&mut advf), &mut back) }.expect("GetAdvise");
assert_eq!(aspects, DVASPECT_CONTENT.0);
assert_eq!(advf, ADVF_ONLYONCE.0 as u32);
assert_eq!(back, Some(sink), "a different object came back");
unsafe { panel.GetAdvise(None, None, core::ptr::null_mut()) }
.expect("GetAdvise with nothing asked for");
}
#[implement(IAdviseSink)]
struct Watcher(Rc<Cell<u32>>);
impl IAdviseSink_Impl for Watcher_Impl {
fn OnViewChange(&self, _aspect: u32, _index: i32) {
self.0.set(self.0.get() + 1);
}
fn OnDataChange(&self, _format: *const FORMATETC, _medium: *const STGMEDIUM) {}
fn OnRename(&self, _moniker: windows_core::Ref<'_, IMoniker>) {}
fn OnSave(&self) {}
fn OnClose(&self) {}
}
fn bounds(left: i32, top: i32, right: i32, bottom: i32) -> RECTL {
RECTL {
left,
top,
right,
bottom,
}
}
fn draw(panel: &IViewObject2, canvas: &Canvas, rect: RECTL) -> windows_core::Result<()> {
unsafe {
panel.Draw(
DVASPECT_CONTENT,
-1,
core::ptr::null_mut(),
None,
None,
canvas.dc,
Some(&rect),
None,
None,
0,
)
}
}
fn put_caption(object: &IDispatch, text: &str) {
let name: Vec<u16> = "Caption"
.encode_utf16()
.chain(core::iter::once(0))
.collect();
let mut dispid = 0i32;
unsafe {
object.GetIDsOfNames(
&GUID::zeroed(),
&windows_core::PCWSTR(name.as_ptr()),
1,
0,
&mut dispid,
)
}
.expect("Caption");
let mut argument = VARIANT {
Anonymous: VARIANT_0 {
Anonymous: ManuallyDrop::new(VARIANT_0_0 {
vt: VARENUM(VT_BSTR.0),
wReserved1: 0,
wReserved2: 0,
wReserved3: 0,
Anonymous: VARIANT_0_0_0 {
bstrVal: ManuallyDrop::new(BSTR::from(text)),
},
}),
},
};
let mut named = DISPID_PROPERTYPUT;
let params = DISPPARAMS {
rgvarg: &mut argument,
rgdispidNamedArgs: &mut named,
cArgs: 1,
cNamedArgs: 1,
};
unsafe {
object.Invoke(
dispid,
&GUID::zeroed(),
0,
DISPATCH_PROPERTYPUT,
¶ms,
None,
None,
None,
)
}
.expect("put Caption");
unsafe {
let _ = VariantClear(&mut argument);
}
}
struct Canvas {
dc: HDC,
bitmap: HBITMAP,
previous: HGDIOBJ,
pixels: *mut u32,
count: usize,
}
impl Canvas {
fn new(width: i32, height: i32) -> Self {
let mut info = BITMAPINFO::default();
info.bmiHeader.biSize = size_of::<BITMAPINFOHEADER>() as u32;
info.bmiHeader.biWidth = width;
info.bmiHeader.biHeight = -height;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biBitCount = 32;
info.bmiHeader.biCompression = BI_RGB.0;
let mut bits = core::ptr::null_mut();
let bitmap = unsafe { CreateDIBSection(None, &info, DIB_RGB_COLORS, &mut bits, None, 0) }
.expect("a DIB section");
let dc = unsafe { CreateCompatibleDC(None) };
let previous = unsafe { SelectObject(dc, bitmap.into()) };
let canvas = Self {
dc,
bitmap,
previous,
pixels: bits.cast(),
count: (width * height) as usize,
};
unsafe { core::slice::from_raw_parts_mut(canvas.pixels, canvas.count) }.fill(SENTINEL);
canvas
}
fn pixels(&self) -> Vec<u32> {
unsafe {
let _ = GdiFlush();
};
unsafe { core::slice::from_raw_parts(self.pixels, self.count) }.to_vec()
}
}
impl Drop for Canvas {
fn drop(&mut self) {
unsafe {
SelectObject(self.dc, self.previous);
let _ = DeleteDC(self.dc);
let _ = DeleteObject(self.bitmap.into());
}
}
}