/** * Toolbar controllers for the formatting toolbar — counterpart of * svx/source/tbxctrls/tbcontrl.cxx (SvxStyleToolBoxControl, * SvxFontNameToolBoxControl, SvxFontSizeBox) and * svx/source/tbxctrls/tbxcolor.cxx / PaletteManager (colour split buttons). */ import { useEffect, useState } from "react"; import { registerToolBoxController, tooltipFor, type ToolBoxControllerProps, } from "../../../framework/source/uielement/toolbarmanager"; import { useDispatcher } from "../../../sfx2/source/control/dispatch"; import { useViewFrame } from "../../../sfx2/source/view/viewfrm"; import { FONT_LIST, FONT_SIZES, cssFontFamily } from "../../../unotools/source/misc/fontdefs"; import { ComboBox } from "../../../vcl/source/control/combobox"; import { CommandImage } from "../../../vcl/source/image/ImplImageTree"; import { ToolBoxSplitButton } from "../../../vcl/source/window/toolbox"; export interface StyleBoxEntry { uiName: string; preview?: { fontFamily?: string; fontSize?: string; fontWeight?: number }; } /** Paragraph style box (.uno:StyleApply). State value: { current, styles }. */ export function SvxStyleBox({ command }: ToolBoxControllerProps) { const dispatcher = useDispatcher(); const frame = useViewFrame(); const state = dispatcher.queryState(command); const value = state.value as { current: string; styles: StyleBoxEntry[] } | undefined; const styles = value?.styles ?? []; return ( s.uiName)} optionStyle={(o) => styles.find((s) => s.uiName === o)?.preview} onCommit={(v) => dispatcher.execute(command, { Style: v, FamilyName: "ParagraphStyles" })} onDone={() => frame.grabFocus()} /> ); } /** Font name box (.uno:CharFontName). */ export function SvxFontNameBox({ command }: ToolBoxControllerProps) { const dispatcher = useDispatcher(); const frame = useViewFrame(); const state = dispatcher.queryState(command); return ( ({ fontFamily: cssFontFamily(o), fontSize: 14 })} onCommit={(v) => dispatcher.execute(command, { CharFontName: v })} onDone={() => frame.grabFocus()} /> ); } /** Font size box (.uno:FontHeight). */ export function SvxFontSizeBox({ command }: ToolBoxControllerProps) { const dispatcher = useDispatcher(); const frame = useViewFrame(); const state = dispatcher.queryState(command); const size = state.value as number | undefined; return ( `${s} pt`)} onCommit={(v) => { const n = parseFloat(v); if (n >= 1 && n <= 999.9) dispatcher.execute(command, { FontHeight: Math.round(n * 10) / 10 }); }} onDone={() => frame.grabFocus()} /> ); } /** LibreOffice's "standard" palette (extras/source/palettes/standard.soc), abridged. */ export const STANDARD_PALETTE: readonly (readonly [string, string])[] = [ ["Black", "#000000"], ["Dark Gray 3", "#333333"], ["Dark Gray 2", "#666666"], ["Gray", "#808080"], ["Light Gray 1", "#999999"], ["Light Gray 2", "#b2b2b2"], ["Light Gray 3", "#cccccc"], ["Light Gray 4", "#dddddd"], ["Light Gray 5", "#eeeeee"], ["White", "#ffffff"], ["Dark Red 2", "#8d281e"], ["Dark Blue 2", "#1e3b5c"], ["Yellow", "#ffff00"], ["Gold", "#ffbf00"], ["Orange", "#ff8000"], ["Brick", "#ff4000"], ["Red", "#ff0000"], ["Magenta", "#bf0041"], ["Purple", "#800080"], ["Indigo", "#55308d"], ["Blue", "#2a6099"], ["Teal", "#158466"], ["Green", "#00a933"], ["Lime", "#81d41a"], ["Light Yellow 2", "#ffffa6"], ["Light Gold 2", "#ffe994"], ["Light Orange 2", "#ffb66c"], ["Light Brick 2", "#ffaa95"], ["Light Red 2", "#ffa6a6"], ["Light Magenta 2", "#ec9ba4"], ["Light Purple 2", "#bf819e"], ["Light Indigo 2", "#b7b3ca"], ["Light Blue 2", "#b4c7dc"], ["Light Teal 2", "#b3cac7"], ["Light Green 2", "#afd095"], ["Light Lime 2", "#e8f2a1"], ["Dark Yellow 2", "#996600"], ["Dark Gold 2", "#b47804"], ["Dark Orange 2", "#b85c00"], ["Dark Brick 2", "#be480a"], ["Dark Red 1", "#c9211e"], ["Dark Magenta 2", "#861141"], ["Dark Purple 2", "#650953"], ["Dark Indigo 2", "#43215f"], ["Dark Blue 1", "#355269"], ["Dark Teal 2", "#1e6a39"], ["Dark Green 2", "#127622"], ["Dark Lime 2", "#468a1a"], ]; const LAST_COLOR: Record = { ".uno:FontColor": "#c9211e", ".uno:CharBackColor": "#ffff00", }; /** Colour split button with palette popup (SvxColorToolBoxControl). */ export function SvxColorToolBoxControl({ command }: ToolBoxControllerProps) { const dispatcher = useDispatcher(); const state = dispatcher.queryState(command); const [last, setLast] = useState(LAST_COLOR[command] ?? "#000000"); const [popup, setPopup] = useState<{ x: number; y: number } | null>(null); const noneLabel = command === ".uno:FontColor" ? "Automatic" : "No Fill"; useEffect(() => { if (!popup) return; const onDown = (e: MouseEvent) => { if (!(e.target as Element).closest(".svx-color-window")) setPopup(null); }; document.addEventListener("mousedown", onDown, true); return () => document.removeEventListener("mousedown", onDown, true); }, [popup]); const apply = (color: string | null) => { setPopup(null); if (color) { setLast(color); LAST_COLOR[command] = color; } dispatcher.execute(command, { Color: color }); }; return ( <> } tooltip={tooltipFor(command)} disabled={!state.enabled} onClick={() => apply(last)} onDropDown={(e) => { const r = e.currentTarget.parentElement!.getBoundingClientRect(); setPopup(popup ? null : { x: r.left, y: r.bottom + 2 }); }} /> {popup && (
e.preventDefault()} >
{STANDARD_PALETTE.map(([name, color]) => (
)} ); } export function registerSvxToolBoxControllers(): void { registerToolBoxController(".uno:StyleApply", SvxStyleBox); registerToolBoxController(".uno:CharFontName", SvxFontNameBox); registerToolBoxController(".uno:FontHeight", SvxFontSizeBox); registerToolBoxController(".uno:FontColor", SvxColorToolBoxControl); registerToolBoxController(".uno:CharBackColor", SvxColorToolBoxControl); }