impress: 增强排练计时(双计时/模式/位置/全局/无障碍) #2

Open
wangxin wants to merge 1 commits from wangxin/loongoffice:feature/impress-rehearse-timer-enhancement into loongoffice-25-2
14 changed files with 654 additions and 52 deletions

View File

@ -639,6 +639,44 @@
</info>
<value>false</value>
</prop>
<prop oor:name="RehearseTimingsMode" oor:type="xs:int" oor:nillable="false">
<info>
<desc>Specifies the timing display mode for rehearsal: 0=Current slide/Total time, 1=Current slide only, 2=Total time only.</desc>
<label>Rehearse timings display mode</label>
</info>
<constraints>
<minInclusive oor:value="0">
<info>
<desc>Minimum value is 0 (Current slide/Total time).</desc>
</info>
</minInclusive>
<maxInclusive oor:value="2">
<info>
<desc>Maximum value is 2 (Total time only).</desc>
</info>
</maxInclusive>
</constraints>
<value>0</value>
</prop>
<prop oor:name="RehearseTimingsPosition" oor:type="xs:int" oor:nillable="false">
<info>
<desc>Specifies the position where the rehearsal timer will be displayed: 0=Top left, 1=Bottom left, 2=Top center, 3=Bottom center, 4=Top right, 5=Bottom right.</desc>
<label>Rehearse timings timer position</label>
</info>
<constraints>
<minInclusive oor:value="0">
<info>
<desc>Minimum value is 0 (Top left).</desc>
</info>
</minInclusive>
<maxInclusive oor:value="5">
<info>
<desc>Maximum value is 5 (Bottom right).</desc>
</info>
</maxInclusive>
</constraints>
<value>0</value>
</prop>
</group>
<group oor:name="Compatibility">
<info>

View File

@ -113,6 +113,9 @@ namespace sd
bool mbShowPauseLogo;
bool mbStartCustomShow;
bool mbInteractive;
bool mnRehearseTimerGlobalSetting; // 是否使用全局计时器设置
sal_Int32 mnTimerMode; // 计时器显示模式: 0=当前/总时间, 1=当前幻灯片, 2=总时间
sal_Int32 mnTimerPosition; // 计时器位置: 0=左上, 1=左下, 2=中上, 3=中下, 4=右上, 5=右下
PresentationSettings();
};

View File

@ -56,8 +56,11 @@ class XColorItem;
#define ATTR_PRESENT_SHOW_PAUSELOGO ATTR_PRESENT_START + 14
#define ATTR_PRESENT_DISPLAY TypedWhichId<SfxInt32Item>(ATTR_PRESENT_START + 15)
#define ATTR_PRESENT_INTERACTIVE ATTR_PRESENT_START + 16
#define ATTR_PRESENT_TIMER_GLOBAL_SETTING TypedWhichId<SfxBoolItem>(ATTR_PRESENT_START + 17)
#define ATTR_PRESENT_TIMING_MODE TypedWhichId<SfxInt32Item>(ATTR_PRESENT_START + 18)
#define ATTR_PRESENT_TIMER_POSITION TypedWhichId<SfxInt32Item>(ATTR_PRESENT_START + 19)
#define ATTR_PRESENT_END ATTR_PRESENT_INTERACTIVE
#define ATTR_PRESENT_END ATTR_PRESENT_TIMER_POSITION
// animation attributes
#define ATTR_ANIMATION_START ATTR_PRESENT_END + 1

View File

@ -112,8 +112,35 @@ PresentationSettings::PresentationSettings()
mnPauseTimeout( 0 ),
mbShowPauseLogo( false ),
mbStartCustomShow( false ),
mbInteractive( true )
mbInteractive( true ),
mnRehearseTimerGlobalSetting(true), // 默认排练计时器全局设置为True
mnTimerMode( 0 ), // 默认显示当前/总时间
mnTimerPosition( 0 ) // 默认位置左上
{
// 从全局配置文件读取排练计时器设置
if (mnRehearseTimerGlobalSetting)
{
try
{
mnTimerMode = officecfg::Office::Impress::Misc::Start::RehearseTimingsMode::get();
mnTimerPosition = officecfg::Office::Impress::Misc::Start::RehearseTimingsPosition::get();
// 校验范围,防止配置文件被手动修改为非法值
if (mnTimerMode < 0 || mnTimerMode > 2)
{
mnTimerMode = 0;
}
if (mnTimerPosition < 0 || mnTimerPosition > 5)
{
mnTimerPosition = 0;
}
}
catch (const css::uno::Exception& e)
{
// 保持默认值 0, 0
}
}
}
SdDrawDocument::SdDrawDocument(DocumentType eType, SfxObjectShell* pDrDocSh)

View File

@ -79,6 +79,9 @@ SdStartPresentationDlg::SdStartPresentationDlg(weld::Window* pWindow, const SfxI
, m_xAllMonitors(m_xBuilder->weld_label(u"allmonitors_str"_ustr))
, m_xMonitorExternal(m_xBuilder->weld_label(u"externalmonitor_str"_ustr))
, m_xExternal(m_xBuilder->weld_label(u"external_str"_ustr))
, m_xCbxTimerGlobalSettings(m_xBuilder->weld_check_button(u"globalrehearsetimings"_ustr) )
, m_xLbTimerMode(m_xBuilder->weld_combo_box(u"timingmode_cb"_ustr))
, m_xLbTimerPosition(m_xBuilder->weld_combo_box(u"timerposition_cb"_ustr))
{
m_xFormatter->SetExtFormat(ExtTimeFieldFormat::LongDuration);
m_xFormatter->EnableEmptyField(false);
@ -180,6 +183,45 @@ SdStartPresentationDlg::SdStartPresentationDlg(weld::Window* pWindow, const SfxI
m_xCbxInteractiveMode->set_active( static_cast<const SfxBoolItem&>( rOutAttrs.Get( ATTR_PRESENT_INTERACTIVE ) ).GetValue() );
// 初始化全局设置复选框状态
bool bGlobalSettings = static_cast<const SfxBoolItem&>( rOutAttrs.Get( ATTR_PRESENT_TIMER_GLOBAL_SETTING ) ).GetValue();
m_xCbxTimerGlobalSettings->set_active(bGlobalSettings);
// 初始化计时器设置
sal_Int32 nTimerMode = 0; // Default to "Current slide / Total time" (matches drawdoc.cxx)
sal_Int32 nTimerPosition = 0; // Default to "Top left" (matches drawdoc.cxx)
// 如果启用全局设置,则从配置中读取,否则使用文档中的值
if (bGlobalSettings) {
nTimerMode = officecfg::Office::Impress::Misc::Start::RehearseTimingsMode::get();
nTimerPosition = officecfg::Office::Impress::Misc::Start::RehearseTimingsPosition::get();
} else {
// 从配置文件读取计时器设置
const SfxPoolItem* pItem;
if (rOutAttrs.GetItemState(ATTR_PRESENT_TIMING_MODE, true, &pItem) == SfxItemState::SET) {
nTimerMode = static_cast<const SfxInt32Item*>(pItem)->GetValue();
}
if (rOutAttrs.GetItemState(ATTR_PRESENT_TIMER_POSITION, true, &pItem) == SfxItemState::SET) {
nTimerPosition = static_cast<const SfxInt32Item*>(pItem)->GetValue();
}
}
// 校验计时器设置范围,如果超出范围,则重置为默认值
if (nTimerMode < 0 || nTimerMode > 2)
nTimerMode = 0; // Reset to default if invalid
if (nTimerPosition < 0 || nTimerPosition > 5)
nTimerPosition = 0; // Reset to default if invalid
// 计时器设置赋值
if (m_xLbTimerMode) {
m_xLbTimerMode->set_active(nTimerMode);
}
if (m_xLbTimerPosition) {
m_xLbTimerPosition->set_active(nTimerPosition);
}
InitMonitorSettings();
ChangeRangeHdl(*m_xRbtCustomshow);
@ -211,6 +253,13 @@ short SdStartPresentationDlg::run()
m_xCbxShowNavigationButton->get_active(), batch);
officecfg::Office::Impress::Layout::Display::NavigationBtnScale::set(
m_xLbNavigationButtonsSize->get_active(), batch);
bool bGlobalSettings = m_xCbxTimerGlobalSettings->get_active();
if (bGlobalSettings){
officecfg::Office::Impress::Misc::Start::RehearseTimingsMode::set(
m_xLbTimerMode->get_active(), batch);
officecfg::Office::Impress::Misc::Start::RehearseTimingsPosition::set(
m_xLbTimerPosition->get_active(), batch);
}
#ifdef ENABLE_SDREMOTE
officecfg::Office::Impress::Misc::Start::EnableSdremote::set(m_xCbxEnableRemote->get_active(), batch);
@ -340,6 +389,26 @@ void SdStartPresentationDlg::GetAttr( SfxItemSet& rAttr )
rAttr.Put( SfxUInt32Item ( ATTR_PRESENT_PAUSE_TIMEOUT, m_xFormatter->GetTime().GetMSFromTime() / 1000 ) );
rAttr.Put( SfxBoolItem ( ATTR_PRESENT_SHOW_PAUSELOGO, m_xCbxAutoLogo->get_active() ) );
rAttr.Put( SfxBoolItem ( ATTR_PRESENT_INTERACTIVE, m_xCbxInteractiveMode->get_active() ) );
rAttr.Put( SfxBoolItem ( ATTR_PRESENT_TIMER_GLOBAL_SETTING, m_xCbxTimerGlobalSettings->get_active() ) );
// 保存计时器的属性值
sal_Int32 nTimingMode = 0;
sal_Int32 nTimerPosition = 0;
// 从下拉列表中获取计时器的属性值
if (m_xLbTimerMode)
nTimingMode = m_xLbTimerMode->get_active();
if (m_xLbTimerPosition)
nTimerPosition = m_xLbTimerPosition->get_active();
// 校验计时器属性值的合法性
if (nTimingMode < 0 || nTimingMode > 2)
nTimingMode = 0; // Reset to default if invalid
if (nTimerPosition < 0 || nTimerPosition > 5)
nTimerPosition = 0; // Reset to default if invalid
rAttr.Put( SfxInt32Item ( ATTR_PRESENT_TIMING_MODE, nTimingMode ) );
rAttr.Put( SfxInt32Item ( ATTR_PRESENT_TIMER_POSITION, nTimerPosition ) );
int nPos = m_xLBMonitor->get_active();
if (nPos != -1)

View File

@ -108,6 +108,9 @@ void FuSlideShowDlg::DoExecute( SfxRequest& )
aDlgSet.Put( SfxUInt32Item( ATTR_PRESENT_PAUSE_TIMEOUT, rPresentationSettings.mnPauseTimeout ) );
aDlgSet.Put( SfxBoolItem( ATTR_PRESENT_SHOW_PAUSELOGO, rPresentationSettings.mbShowPauseLogo ) );
aDlgSet.Put( SfxBoolItem( ATTR_PRESENT_INTERACTIVE, rPresentationSettings.mbInteractive ) );
aDlgSet.Put( SfxBoolItem( ATTR_PRESENT_TIMER_GLOBAL_SETTING, rPresentationSettings.mnRehearseTimerGlobalSetting ) );
aDlgSet.Put( SfxInt32Item( ATTR_PRESENT_TIMING_MODE, rPresentationSettings.mnTimerMode ) );
aDlgSet.Put( SfxInt32Item( ATTR_PRESENT_TIMER_POSITION, rPresentationSettings.mnTimerPosition ) );
SdOptions* pOptions = SdModule::get()->GetSdOptions(DocumentType::Impress);
aDlgSet.Put( SfxInt32Item( ATTR_PRESENT_DISPLAY, pOptions->GetDisplay() ) );
@ -220,6 +223,27 @@ void FuSlideShowDlg::DoExecute( SfxRequest& )
rPresentationSettings.mbInteractive = bValue;
}
bValue = ITEMVALUE( aDlgSet, ATTR_PRESENT_TIMER_GLOBAL_SETTING, SfxBoolItem );
if ( bValue != rPresentationSettings.mnRehearseTimerGlobalSetting ) {
bValuesChanged = true;
rPresentationSettings.mnRehearseTimerGlobalSetting = bValue;
}
// Handle timing mode setting
nValue32 = ITEMVALUE( aDlgSet, ATTR_PRESENT_TIMING_MODE, SfxInt32Item );
if ( nValue32 != rPresentationSettings.mnTimerMode )
{
bValuesChanged = true;
rPresentationSettings.mnTimerMode = nValue32;
}
nValue32 = ITEMVALUE( aDlgSet, ATTR_PRESENT_TIMER_POSITION, SfxInt32Item );
if ( nValue32 != rPresentationSettings.mnTimerPosition )
{
bValuesChanged = true;
rPresentationSettings.mnTimerPosition = nValue32;
}
bValue = ITEMVALUE( aDlgSet, ATTR_PRESENT_PEN, SfxBoolItem );
if ( bValue != rPresentationSettings.mbMouseAsPen )
{

View File

@ -72,6 +72,11 @@ private:
std::unique_ptr<weld::Label> m_xMonitorExternal;
std::unique_ptr<weld::Label> m_xExternal;
// Rehearse timings controls
std::unique_ptr<weld::CheckButton> m_xCbxTimerGlobalSettings;
std::unique_ptr<weld::ComboBox> m_xLbTimerMode;
std::unique_ptr<weld::ComboBox> m_xLbTimerPosition;
DECL_LINK(ChangeRemoteHdl, weld::Toggleable&, void);
DECL_LINK(ChangeRangeHdl, weld::Toggleable&, void);
DECL_LINK(ClickWindowPresentationHdl, weld::Toggleable&, void);

View File

@ -123,6 +123,8 @@ static std::span<const SfxItemPropertyMapEntry> ImplGetPresentationPropertyMap()
{ u"Pause"_ustr, ATTR_PRESENT_PAUSE_TIMEOUT, ::cppu::UnoType<sal_Int32>::get(), 0, 0 },
{ u"StartWithNavigator"_ustr, ATTR_PRESENT_NAVIGATOR, cppu::UnoType<bool>::get(), 0, 0 },
{ u"UsePen"_ustr, ATTR_PRESENT_PEN, cppu::UnoType<bool>::get(), 0, 0 },
{ u"TimingMode"_ustr, ATTR_PRESENT_TIMING_MODE, ::cppu::UnoType<sal_Int32>::get(), 0, 0 },
{ u"TimerPosition"_ustr, ATTR_PRESENT_TIMER_POSITION, ::cppu::UnoType<sal_Int32>::get(), 0, 0 },
};
return aPresentationPropertyMap_Impl;
@ -529,6 +531,37 @@ void SAL_CALL SlideShow::setPropertyValue( const OUString& aPropertyName, const
}
break;
}
// 添加计时模式设置
case ATTR_PRESENT_TIMING_MODE:
{
sal_Int32 nValue = 0;
if( (aValue >>= nValue) && (nValue >= 0 && nValue <= 2) )
{
bIllegalArgument = false;
if( rPresSettings.mnTimerMode != nValue )
{
bValuesChanged = true;
rPresSettings.mnTimerMode = nValue;
}
}
break;
}
// 添加计时器位置设置
case ATTR_PRESENT_TIMER_POSITION:
{
sal_Int32 nValue = 0;
if( (aValue >>= nValue) && (nValue >= 0 && nValue <= 5) )
{
bIllegalArgument = false;
if( rPresSettings.mnTimerPosition != nValue )
{
bValuesChanged = true;
rPresSettings.mnTimerPosition = nValue;
}
}
break;
}
default:
throw UnknownPropertyException( OUString::number(pEntry ? pEntry->nWID : -1), static_cast<cppu::OWeakObject*>(this));
@ -601,6 +634,10 @@ Any SAL_CALL SlideShow::getPropertyValue( const OUString& PropertyName )
SdOptions* pOptions = SdModule::get()->GetSdOptions(DocumentType::Impress);
return Any(pOptions->GetDisplay());
}
case ATTR_PRESENT_TIMING_MODE:
return Any( rPresSettings.mnTimerMode );
case ATTR_PRESENT_TIMER_POSITION:
return Any( rPresSettings.mnTimerPosition );
default:
throw UnknownPropertyException( OUString::number(pEntry ? pEntry->nWID : -1), static_cast<cppu::OWeakObject*>(this));
@ -835,7 +872,16 @@ void SAL_CALL SlideShow::end()
void SAL_CALL SlideShow::rehearseTimings()
{
Sequence< PropertyValue > aArguments{ comphelper::makePropertyValue(u"RehearseTimings"_ustr, true) };
// Get the presentation settings to retrieve timing mode and timer position
const PresentationSettings& rSettings = mpDoc->getPresentationSettings();
// Create arguments array with RehearseTimings, TimingMode, and TimerPosition
Sequence< PropertyValue > aArguments{
comphelper::makePropertyValue(u"RehearseTimings"_ustr, true),
comphelper::makePropertyValue(u"TimingMode"_ustr, rSettings.mnTimerMode),
comphelper::makePropertyValue(u"TimerPosition"_ustr, rSettings.mnTimerPosition)
};
startWithArguments( aArguments );
}

View File

@ -554,6 +554,8 @@ SlideshowImpl::SlideshowImpl( const Reference< XPresentation2 >& xPresentation,
, mbWasPaused(false)
, mbInputFreeze(false)
, mbActive(false)
, mnTimerMode(0) // 默认显示当前/总时间
, mnTimerPosition(0) // 默认位置左上
, maPresSettings( pDoc->getPresentationSettings() )
, mnUserPaintColor( 0x80ff0000L )
, mbUsePen(false)
@ -1061,6 +1063,8 @@ bool SlideshowImpl::startShow( PresentationSettingsEx const * pPresSettings )
{
maPresSettings = *pPresSettings;
mbRehearseTimings = pPresSettings->mbRehearseTimings;
mnTimerMode = pPresSettings->mnTimerMode;
mnTimerPosition = pPresSettings->mnTimerPosition;
}
OUString aPresSlide( maPresSettings.maPresPage );
@ -1220,6 +1224,10 @@ bool SlideshowImpl::startShow( PresentationSettingsEx const * pPresSettings )
if (mbRehearseTimings) {
aProperties.emplace_back( "RehearseTimings" ,
-1, Any(true), beans::PropertyState_DIRECT_VALUE );
aProperties.emplace_back( "TimingMode" ,
-1, Any(mnTimerMode), beans::PropertyState_DIRECT_VALUE );
aProperties.emplace_back( "TimerPosition" ,
-1, Any(mnTimerPosition), beans::PropertyState_DIRECT_VALUE );
}
bRet = startShowImpl( Sequence<beans::PropertyValue>(
@ -3557,6 +3565,8 @@ PresentationSettingsEx::PresentationSettingsEx( const PresentationSettingsEx& r
, mbRehearseTimings(r.mbRehearseTimings)
, mbPreview(r.mbPreview)
, mpParentWindow( nullptr )
, mnTimerMode(r.mnTimerMode)
, mnTimerPosition(r.mnTimerPosition)
{
}
@ -3565,14 +3575,22 @@ PresentationSettingsEx::PresentationSettingsEx( PresentationSettings const & r )
, mbRehearseTimings(false)
, mbPreview(false)
, mpParentWindow(nullptr)
, mnTimerMode(r.mnTimerMode) // 从基础设置获取计时器显示模式
, mnTimerPosition(r.mnTimerPosition) // 从基础设置获取计时器位置
{
}
void PresentationSettingsEx::SetArguments( const Sequence< PropertyValue >& rArguments )
{
for( const PropertyValue& rValue : rArguments )
// for( const PropertyValue& rValue : rArguments )
// {
// SetPropertyValue( rValue.Name, rValue.Value );
// }
for (const PropertyValue& rValue : rArguments)
{
SetPropertyValue( rValue.Name, rValue.Value );
OUString aName = rValue.Name;
SetPropertyValue(aName, rValue.Value);
}
}
@ -3663,6 +3681,16 @@ void PresentationSettingsEx::SetPropertyValue( std::u16string_view rProperty, co
if( rValue >>= mbMouseAsPen )
return;
}
else if ( rProperty == u"TimingMode" )
{
if( rValue >>= mnTimerMode )
return;
}
else if ( rProperty == u"TimerPosition" )
{
if( rValue >>= mnTimerPosition )
return;
}
throw IllegalArgumentException();
}

View File

@ -58,6 +58,8 @@ struct PresentationSettingsEx : public PresentationSettings
VclPtr<vcl::Window> mpParentWindow;
css::uno::Reference< css::drawing::XDrawPage > mxStartPage;
css::uno::Reference< css::animations::XAnimationNode > mxAnimationNode;
sal_Int32 mnTimerMode;
sal_Int32 mnTimerPosition;
PresentationSettingsEx( const PresentationSettingsEx& );
explicit PresentationSettingsEx( PresentationSettings const & );
@ -340,6 +342,8 @@ private:
bool mbWasPaused; // used to cache pause state during context menu
bool mbInputFreeze;
bool mbActive;
sal_Int32 mnTimerMode; // 计时器显示模式
sal_Int32 mnTimerPosition; // 计时器位置
PresentationSettings maPresSettings;
sal_Int32 mnUserPaintColor;

View File

@ -80,7 +80,7 @@
</packing>
</child>
<child>
<!-- n-columns=1 n-rows=4 -->
<!-- n-columns=1 n-rows=5 -->
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can-focus">False</property>
@ -786,6 +786,142 @@
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="framerehearsetimings">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="label-xalign">0</property>
<property name="shadow-type">none</property>
<child>
<!-- n-columns=2 n-rows=3 -->
<object class="GtkGrid" id="grid11">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">12</property>
<property name="margin-top">6</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<!-- Global Settings Checkbox -->
<child>
<object class="GtkCheckButton" id="globalrehearsetimings">
<property name="label" translatable="yes" context="presentationdialog|globalrehearsetimings">_Global Settings</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
<child internal-child="accessible">
<object class="AtkObject" id="globalrehearsetimings-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="presentationdialog|extended_tip|globalrehearsetimings">Apply rehearsal timing settings globally to all presentations.</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
<property name="width">2</property>
</packing>
</child>
<!-- Timing Mode Label -->
<child>
<object class="GtkLabel" id="timingmode_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes" context="presentationdialog|timingmode_label">_Timing mode:</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">timingmode_cb</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<!-- Timing Mode ComboBox -->
<child>
<object class="GtkComboBoxText" id="timingmode_cb">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<items>
<item id="0" translatable="yes" context="presentationdialog|timingmode_cb">Current slide / Total time</item>
<item id="1" translatable="yes" context="presentationdialog|timingmode_cb">Current slide only</item>
<item id="2" translatable="yes" context="presentationdialog|timingmode_cb">Total time only</item>
</items>
<child internal-child="accessible">
<object class="AtkObject" id="timingmode_cb-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="presentationdialog|extended_tip|timingmode_cb">Select the timing display mode for rehearsal. Choose to display current slide time, total time, or both.</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<!-- Timer Position Label -->
<child>
<object class="GtkLabel" id="timerposition_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes" context="presentationdialog|timerposition_label">Timer _position:</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">timerposition_cb</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<!-- Timer Position ComboBox -->
<child>
<object class="GtkComboBoxText" id="timerposition_cb">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<items>
<item id="0" translatable="yes" context="presentationdialog|timerposition_cb">Top left</item>
<item id="1" translatable="yes" context="presentationdialog|timerposition_cb">Bottom left</item>
<item id="2" translatable="yes" context="presentationdialog|timerposition_cb">Top center</item>
<item id="3" translatable="yes" context="presentationdialog|timerposition_cb">Bottom center</item>
<item id="4" translatable="yes" context="presentationdialog|timerposition_cb">Top right</item>
<item id="5" translatable="yes" context="presentationdialog|timerposition_cb">Bottom right</item>
</items>
<child internal-child="accessible">
<object class="AtkObject" id="timerposition_cb-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="presentationdialog|extended_tip|timerposition_cb">Select the screen position where the rehearsal timer will be displayed during presentation.</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
</object>
</child>
<!-- Frame Label -->
<child type="label">
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes" context="presentationdialog|label6">Rehearse Timings</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frameremote">
<property name="visible">True</property>
@ -864,7 +1000,7 @@
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
<property name="top-attach">4</property>
</packing>
</child>
</object>

View File

@ -17,7 +17,6 @@
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <rtl/ustrbuf.hxx>
#include <vcl/svapp.hxx>
#include <vcl/gdimtf.hxx>
@ -134,12 +133,17 @@ private:
const sal_Int32 LEFT_BORDER_SPACE = 10;
const sal_Int32 LOWER_BORDER_SPACE = 30;
RehearseTimingsActivity::RehearseTimingsActivity( const SlideShowContext& rContext ) :
RehearseTimingsActivity::RehearseTimingsActivity( const SlideShowContext& rContext,
sal_Int32 nTimingMode,
sal_Int32 nTimerPosition ) :
mrEventQueue(rContext.mrEventQueue),
mrScreenUpdater(rContext.mrScreenUpdater),
mrEventMultiplexer(rContext.mrEventMultiplexer),
mrActivitiesQueue(rContext.mrActivitiesQueue),
maElapsedTime( rContext.mrEventQueue.getTimer() ),
maTotalElapsedTime( rContext.mrEventQueue.getTimer() ), // 添加一个总的计时器
mnTimerMode( nTimingMode ), // 计时器显示模式
mnTimerPosition( nTimerPosition ), // 计时器位置
maViews(),
maSpriteRectangle(),
maFont( Application::GetSettings().GetStyleSettings().GetLabelFont() ),
@ -155,15 +159,38 @@ RehearseTimingsActivity::RehearseTimingsActivity( const SlideShowContext& rConte
maFont.SetAlignment( ALIGN_BASELINE );
maFont.SetColor( COL_BLACK );
// determine sprite size (in pixel):
// determine sprite size (in pixel) for dual timing display:
ScopedVclPtrInstance< VirtualDevice > blackHole;
blackHole->EnableOutput(false);
blackHole->SetFont( maFont );
blackHole->SetMapMode(MapMode(MapUnit::MapPixel));
tools::Rectangle rect;
const FontMetric metric( blackHole->GetFontMetric() );
blackHole->GetTextBoundRect( rect, u"XX:XX:XX"_ustr );
maSpriteSizePixel.setX( rect.getOpenWidth() * 12 / 10 );
// 根据计时器显示模式选择不同的样本字符串来计算宽度
OUString aSampleText;
sal_Int32 nWidthFactor; // 宽度系数(分子,分母固定为10)
switch (mnTimerMode)
{
case 1: // 只显示当前幻灯片计时
case 2: // 只显示总时间计时
// 单计时显示:"XX:XX:XX"
aSampleText = u"XX:XX:XX"_ustr;
nWidthFactor = 12; // 1.2倍
break;
case 0: // 显示当前幻灯片/总时间(双计时显示)
default:
// 双计时显示:"XX:XX:XX / XX:XX:XX"
aSampleText = u"XX:XX:XX / XX:XX:XX"_ustr;
nWidthFactor = 11; // 1.1倍
break;
}
// 先计算文本边界
blackHole->GetTextBoundRect( rect, aSampleText );
// 再根据计算结果和对应的系数设置宽度
maSpriteSizePixel.setX( rect.getOpenWidth() * nWidthFactor / 10 );
maSpriteSizePixel.setY( metric.GetLineHeight() * 11 / 10 );
mnYOffset = (metric.GetAscent() + (metric.GetLineHeight() / 20));
@ -184,10 +211,12 @@ RehearseTimingsActivity::~RehearseTimingsActivity()
}
std::shared_ptr<RehearseTimingsActivity> RehearseTimingsActivity::create(
const SlideShowContext& rContext )
const SlideShowContext& rContext,
sal_Int32 nTimingMode,
sal_Int32 nTimerPosition )
{
std::shared_ptr<RehearseTimingsActivity> pActivity(
new RehearseTimingsActivity( rContext ));
new RehearseTimingsActivity( rContext, nTimingMode, nTimerPosition ));
pActivity->mpMouseHandler =
std::make_shared<MouseHandler>(*pActivity);
@ -204,6 +233,11 @@ std::shared_ptr<RehearseTimingsActivity> RehearseTimingsActivity::create(
void RehearseTimingsActivity::start()
{
maElapsedTime.reset();
// 当总时间为0时,重置总时间计时器
if (maTotalElapsedTime.getElapsedTime() == 0.0)
{
maTotalElapsedTime.reset();
}
mbDrawPressed = false;
mbActive = true;
@ -306,11 +340,50 @@ basegfx::B2DRange RehearseTimingsActivity::calcSpriteRectangle( UnoViewSharedPtr
return basegfx::B2DRange();
const geometry::IntegerSize2D realSize( xBitmap->getSize() );
// pixel:
basegfx::B2DPoint spritePos(
std::min<sal_Int32>( realSize.Width, LEFT_BORDER_SPACE ),
std::max<sal_Int32>( 0, realSize.Height - maSpriteSizePixel.getY()
- LOWER_BORDER_SPACE ) );
// 根据计时器位置设置计算显示位置
basegfx::B2DPoint spritePos;
switch (mnTimerPosition)
{
case 0: // 左上
spritePos = basegfx::B2DPoint(
std::min<sal_Int32>( realSize.Width, LEFT_BORDER_SPACE ),
std::min<sal_Int32>( realSize.Height, LOWER_BORDER_SPACE ) );
break;
case 1: // 左下
spritePos = basegfx::B2DPoint(
std::min<sal_Int32>( realSize.Width, LEFT_BORDER_SPACE ),
std::max<sal_Int32>( 0, realSize.Height - maSpriteSizePixel.getY()
- LOWER_BORDER_SPACE ) );
break;
case 2: // 中上
spritePos = basegfx::B2DPoint(
std::max<sal_Int32>( 0, (realSize.Width - maSpriteSizePixel.getX()) / 2 ),
std::min<sal_Int32>( realSize.Height, LOWER_BORDER_SPACE ) );
break;
case 3: // 中下
spritePos = basegfx::B2DPoint(
std::max<sal_Int32>( 0, (realSize.Width - maSpriteSizePixel.getX()) / 2 ),
std::max<sal_Int32>( 0, realSize.Height - maSpriteSizePixel.getY()
- LOWER_BORDER_SPACE ) );
break;
case 4: // 右上
spritePos = basegfx::B2DPoint(
std::max<sal_Int32>( 0, realSize.Width - maSpriteSizePixel.getX()
- LEFT_BORDER_SPACE ),
std::min<sal_Int32>( realSize.Height, LOWER_BORDER_SPACE ) );
break;
case 5: // 右下
default:
spritePos = basegfx::B2DPoint(
std::max<sal_Int32>( 0, realSize.Width - maSpriteSizePixel.getX()
- LEFT_BORDER_SPACE ),
std::max<sal_Int32>( 0, realSize.Height - maSpriteSizePixel.getY()
- LOWER_BORDER_SPACE ) );
break;
}
basegfx::B2DHomMatrix transformation( rView->getTransformation() );
transformation.invert();
spritePos *= transformation;
@ -408,23 +481,98 @@ void RehearseTimingsActivity::paintAllSprites() const
void RehearseTimingsActivity::paint( cppcanvas::CanvasSharedPtr const & canvas ) const
{
// build timer string:
const sal_Int32 nTimeSecs =
// 只在第一次绘制时打印
static bool bFirstPaint = true;
if (bFirstPaint) {
bFirstPaint = false;
}
// build timer strings based on timing mode setting
const sal_Int32 nCurrentSlideTimeSecs =
static_cast<sal_Int32>(maElapsedTime.getElapsedTime());
OUStringBuffer buf;
sal_Int32 n = nTimeSecs / 3600;
if (n < 10)
buf.append( '0' );
buf.append( OUString::number(n) + ":" );
n = ((nTimeSecs % 3600) / 60);
if (n < 10)
buf.append( '0' );
buf.append( OUString::number(n) + ":" );
n = (nTimeSecs % 60);
if (n < 10)
buf.append( '0' );
buf.append( n );
const OUString time = buf.makeStringAndClear();
const sal_Int32 nTotalTimeSecs =
static_cast<sal_Int32>(maTotalElapsedTime.getElapsedTime());
OUString time;
// 根据计时器显示模式设置显示不同的计时信息
switch (mnTimerMode)
{
case 1: // 只显示当前幻灯片计时
{
OUStringBuffer buf;
sal_Int32 n = nCurrentSlideTimeSecs / 3600;
if (n < 10)
buf.append( '0' );
buf.append( OUString::number(n) + ":" );
n = ((nCurrentSlideTimeSecs % 3600) / 60);
if (n < 10)
buf.append( '0' );
buf.append( OUString::number(n) + ":" );
n = (nCurrentSlideTimeSecs % 60);
if (n < 10)
buf.append( '0' );
buf.append( n );
time = buf.makeStringAndClear();
break;
}
case 2: // 只显示总时间计时
{
OUStringBuffer buf;
sal_Int32 n = nTotalTimeSecs / 3600;
if (n < 10)
buf.append( '0' );
buf.append( OUString::number(n) + ":" );
n = ((nTotalTimeSecs % 3600) / 60);
if (n < 10)
buf.append( '0' );
buf.append( OUString::number(n) + ":" );
n = (nTotalTimeSecs % 60);
if (n < 10)
buf.append( '0' );
buf.append( n );
time = buf.makeStringAndClear();
break;
}
case 0: // 显示当前幻灯片/总时间(双计时显示)
default:
{
// 格式化当前幻灯片的排练计时
OUStringBuffer currentBuf;
sal_Int32 n = nCurrentSlideTimeSecs / 3600;
if (n < 10)
currentBuf.append( '0' );
currentBuf.append( OUString::number(n) + ":" );
n = ((nCurrentSlideTimeSecs % 3600) / 60);
if (n < 10)
currentBuf.append( '0' );
currentBuf.append( OUString::number(n) + ":" );
n = (nCurrentSlideTimeSecs % 60);
if (n < 10)
currentBuf.append( '0' );
currentBuf.append( n );
const OUString currentTime = currentBuf.makeStringAndClear();
// 格式化当前总的排练计时
OUStringBuffer totalBuf;
n = nTotalTimeSecs / 3600;
if (n < 10)
totalBuf.append( '0' );
totalBuf.append( OUString::number(n) + ":" );
n = ((nTotalTimeSecs % 3600) / 60);
if (n < 10)
totalBuf.append( '0' );
totalBuf.append( OUString::number(n) + ":" );
n = (nTotalTimeSecs % 60);
if (n < 10)
totalBuf.append( '0' );
totalBuf.append( n );
const OUString totalTime = totalBuf.makeStringAndClear();
// 组装当前幻灯片计时和总计时
time = currentTime + " / " + totalTime;
break;
}
}
// create the MetaFile:
GDIMetaFile metaFile;

View File

@ -53,7 +53,9 @@ public:
/** Creates the activity.
*/
static std::shared_ptr<RehearseTimingsActivity> create(
const SlideShowContext& rContext );
const SlideShowContext& rContext,
sal_Int32 nTimingMode = 0, // 计时器显示模式
sal_Int32 nTimerPosition = 0 ); // 计时器位置
virtual ~RehearseTimingsActivity() override;
RehearseTimingsActivity(const RehearseTimingsActivity&) = delete;
@ -90,7 +92,9 @@ public:
private:
class WakeupEvent;
explicit RehearseTimingsActivity( const SlideShowContext& rContext );
explicit RehearseTimingsActivity( const SlideShowContext& rContext,
sal_Int32 nTimingMode = 0, // 计时器显示模式
sal_Int32 nTimerPosition = 0 ); // 计时器位置
void paint( ::cppcanvas::CanvasSharedPtr const & canvas ) const;
void paintAllSprites() const;
@ -119,6 +123,9 @@ private:
EventMultiplexer& mrEventMultiplexer;
ActivitiesQueue& mrActivitiesQueue;
canvas::tools::ElapsedTime maElapsedTime;
canvas::tools::ElapsedTime maTotalElapsedTime; // 添加总的计时器
sal_Int32 mnTimerMode; // 计时器显示模式
sal_Int32 mnTimerPosition; // 计时器位置
ViewsVecT maViews;

View File

@ -17,7 +17,6 @@
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <config_features.h>
#include <comphelper/diagnose_ex.hxx>
@ -378,6 +377,9 @@ private:
/// stops the current slide transition sound
void stopSlideTransitionSound();
// 创建RehearseTimingsActivity
void maybeCreateOrUpdateRehearseTimingsActivity();
/** Prepare a slide transition
This method registers all necessary events and
@ -494,6 +496,11 @@ private:
bool mbSlideShowIdle;
bool mbDisableAnimationZOrder;
bool mbMovingForward;
bool mbRehearseTimings; // 排练计时功能开关
sal_Int32 mnTimerMode; // 计时器显示模式
sal_Int32 mnTimerPosition; // 计时器位置
bool mbTimerPositionSet; // 计时器显示模式标识
bool mbTimerModeSet; // 计时器位置标识
EffectRewinder maEffectRewinder;
FrameSynchronization maFrameSynchronization;
@ -606,6 +613,11 @@ SlideShowImpl::SlideShowImpl(
mbSlideShowIdle( true ),
mbDisableAnimationZOrder( false ),
mbMovingForward( true ),
mbRehearseTimings( false ), // 默认关闭排练计时
mnTimerMode( 0 ), // 默认显示当前/总时间
mnTimerPosition( 0 ), // 默认位置左上
mbTimerPositionSet(false), // 默认计时器位置未发生改变
mbTimerModeSet(false), // 默认计时器显示模式未发生改变
maEffectRewinder(maEventMultiplexer, maEventQueue, maUserEventQueue),
maFrameSynchronization(1.0 / FrameRate::PreferredFramesPerSecond)
@ -1852,28 +1864,49 @@ sal_Bool SlideShowImpl::setProperty( beans::PropertyValue const& rProperty )
return (rProperty.Value >>= mbForceManualAdvance);
}
if ( rProperty.Name == "TimingMode" )
{
sal_Int32 nTimingMode = 0;
if (! (rProperty.Value >>= nTimingMode))
return false;
mnTimerMode = nTimingMode;
mbTimerModeSet = true;
// 统一调用一个 helper
maybeCreateOrUpdateRehearseTimingsActivity();
return true;
}
if ( rProperty.Name == "TimerPosition" )
{
sal_Int32 nTimerPosition = 0;
if (! (rProperty.Value >>= nTimerPosition))
return false;
mnTimerPosition = nTimerPosition;
mbTimerPositionSet = true;
// 统一调用一个 helper
maybeCreateOrUpdateRehearseTimingsActivity();
return true;
}
if ( rProperty.Name == "RehearseTimings" )
{
bool bRehearseTimings = false;
if (! (rProperty.Value >>= bRehearseTimings))
return false;
mbRehearseTimings = bRehearseTimings;
if (bRehearseTimings)
{
// TODO(Q3): Move to slide
mpRehearseTimingsActivity = RehearseTimingsActivity::create(
SlideShowContext(
mpDummyPtr,
maEventQueue,
maEventMultiplexer,
maScreenUpdater,
maActivitiesQueue,
maUserEventQueue,
*this,
*this,
maViewContainer,
mxComponentContext,
mpBox2DDummyPtr ) );
// 启用排练计时,但不直接创建,让 helper 根据当前参数决定
maybeCreateOrUpdateRehearseTimingsActivity();
}
else if (mpRehearseTimingsActivity)
{
@ -2017,6 +2050,37 @@ sal_Bool SlideShowImpl::setProperty( beans::PropertyValue const& rProperty )
return false;
}
void SlideShowImpl::maybeCreateOrUpdateRehearseTimingsActivity()
{
// 1) 没启用排练计时就不用创建
if (!mbRehearseTimings)
return;
// 2) 两个参数都至少要被设置过一次
if (!mbTimerModeSet || !mbTimerPositionSet)
return;
// 3) 如果还没创建过,就创建一次
if (!mpRehearseTimingsActivity)
{
mpRehearseTimingsActivity = RehearseTimingsActivity::create(
SlideShowContext(
mpDummyPtr,
maEventQueue,
maEventMultiplexer,
maScreenUpdater,
maActivitiesQueue,
maUserEventQueue,
*this,
*this,
maViewContainer,
mxComponentContext,
mpBox2DDummyPtr ),
mnTimerMode,
mnTimerPosition );
}
}
void SlideShowImpl::addSlideShowListener(
uno::Reference<presentation::XSlideShowListener> const& xListener )
{