388 lines
14 KiB

# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'dev make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2023
#
# @@ Meta Begin
# Application winlibreoffice 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
package require uri ;#tcllib
package require punk::lib
#windows? REVIEW - can we provide a common api for other platforms with only script? tcluno instead?
if {"windows" eq $::tcl_platform(platform)} {
if {[catch {package require twapi}]} {
puts stderr "Twapi package required for winlibreoffice to function"
puts stderr "Minimal functionality - only some utils may work"
}
} else {
puts stderr "Package requires twapi. No current equivalent on non-windows platform. Try tcluno http://sf.net/projets/tcluno"
puts stderr "Minimal functionality - only some utils may work"
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval winlibreoffice {
namespace export from_libre_date to_libre_date
#---
#todo: investigate tcluno package http://sf.net/projects/tcluno
#CPlusPlus - platforms?
#---
#
#enable 1
variable datebase "1899-12-30" ;#libreoffice default in options->LibreOfifce Calc->Calculate
#variable datebase "1900-01-01" ;#StarCalc 1.0
#variable datebase "1904-01-01" ;# ???
#sometimes a com object may support $obj -print
#see also
# $obj -destroy
# $obj Quit
# $collection -iterate ?options? varname script
variable uno "" ;# service manager object
variable psm "" ;# process service manager
# -- --- --- ---
# libreoffice functions
proc getServiceManager {} {
variable uno
if {$uno eq ""} {
set uno [twapi::comobj com.sun.star.ServiceManager]
}
return $uno
}
#uno getAvailableServiceNames
#e.g com.sun.star.beans.Introspection
# com.sun.star.ucb.SimpleFileAccess
proc createUnoService {objname} {
[getProcessServiceManager] createInstance $objname
}
proc getProcessServiceManager {} {
variable psm
if {$psm eq ""} {
set svcmgr [getServiceManager]
#set psm [$svcmgr getProcessServiceManager]
#seems to be same object? - it has createInstance anyway REVIEW
set psm $svcmgr
}
return $psm
}
#what does libreoffice accept for this fun.. local file paths only?
proc convertToUrl {fpath} {
if {![string match "file:/*" $fpath]} {
# this turns //server/blah to file:////server/blah - which is probably nonsense
set fpath [uri::join scheme file path $fpath]
}
return $fpath
}
#
proc convertFromUrl {fileuri} {
if {[string match "file:/*" $fileuri]} {
set finfo [uri::split $fileuri]
if {"windows" eq $::tcl_platform(platform)} {
if {[dict exists $finfo host]} {
return "//${host}${path}"
} else {
#the leading slash in path indicates a local path and we strip on windows
set p [dict get $finfo path]
if {[string index $p 0] eq "/"} {
set p [string range $p 1 end]
}
return $p
}
} else {
if {[dict exists $finfo host]} {
#?? review - how are file uris to other hosts handled?
error "convertFromUrl doesn't handle non-local file uris on this platform"
} else {
return [dict get $finfo path]
}
}
}
}
# -- --- --- ---
# custom functions
proc get_desktop {} {
set uno [getServiceManager]
set ctx [$uno getPropertyValue "DefaultContext"]
set dt [$ctx getByName /singletons/com.sun.star.frame.theDesktop]
#$dt setName odk_officedev_desk
#$dt getName
return $dt
}
proc blankdoc {{type scalc}} {
set known_types [list scalc swriter simpress sdraw smath]
if {$type ni $known_types} {
puts stderr "Warning: unknown type $type. (known types: $known_types) will try anyway - private:factory/$type"
}
set dt [get_desktop]
set doc [$dt loadComponentFromUrl "private:factory/$type" "_blank" 0 ""] ;#doesn't work without final param - empty string seems to work
puts "doc title: [$doc Title]"
#title can be set with [$doc settitle "titletext"]
return $doc
}
proc file_open_dialog {{title "pick a libreoffice file"}} {
set filepicker [createUnoService "com.sun.star.ui.dialogs.FilePicker"]
$filepicker Title $title
set result [$filepicker Execute]
if {$result} {
#set files [$filepicker getSelectedFiles]
# -iterate ?
# return files(0) ?
#e.g file:///C:/Users/sleek/test.txt
return [$filepicker getFiles]
} else {
return ""
}
}
#todo oo interface?
proc calcdoc_sheets_by_index {doc idx} {
set sheets [$doc getSheets]
set s [$sheets getByIndex $idx]
puts stdout "Sheet: [$s getName]"
#set name with [$s setName "xxx"]
return $s
}
proc calcsheet_cell_range_by_name {sheet rangename} {
return [$sheet getCellRangeByName $rangename] ;#e.g A1
}
proc calccell_setString {cell str} {
$cell setString $str
}
proc calccell_setValue {cell value} {
$cell setValue $value
}
proc calccell_setPropertyValue {cell propset} {
$cell setPropertyValue {*}$propset
#e.g "NumberFormat" 49
# YYYY-MM-DD
#can also use in this case [$cell NumberFormat]
}
proc calccell_setCellBackColorRGB {cell rgb} {
set rgb [string trim $rgb #]
set dec [punk::lib::hex2dec $rgb]
$cell setPropertyValue "CellBackColor" [expr {$dec}] ;#colour value must be integer - will fail if string
}
proc calccell_setCharColorRGB {cell rgb} {
set rgb [string trim $rgb #]
set dec [punk::lib::hex2dec $rgb]
$cell setPropertyValue "CharColor" [expr {$dec}]
}
#cell charFontName
#cell charWeight
#com.sun.star.awt.FontWeight
#https://api.libreoffice.org/docs/idl/ref/FontWeight_8idl.html
# values are listed with 6 DPs - but one seems to work
# only setting to normal and bold seem to result in a value (regular & bold) in the format->font style dialog for the cell.
#DONTKNOW 0.0
#THIN 50.0
#ULTRALIGHT 60.0
#LIGHT 75.0
#SEMILIGHT 90.0
#NORMAL 100.0
#SEMIBOLD 110.0
#BOLD 150.0
#ULTRABOLD 175.0
#BLACK 200.0
lappend PUNKARGS [list {
@id -id ::winlibreoffice::to_libre_date
@cmd -name winlibreoffice::to_libre_date -help\
"Return an internal Libre Office date/time floating point
number representing the number of days between 1899-12-30
and the supplied time.
e.g
% to_libre_date 2025-02-28T060000
45716.25
% to_libre_date 2025-01-01T060101
45658.250706018516
"
@opts
-timezone -default "" -help\
"If unspecified, the timezone will be the
current time zone on the system"
@values -min 1 -max 1
time -type string -help\
"A unix timestamp as output by 'clock seconds'
or a text timestamp such as 2025-01-03T000000
parseable by 'clock scan'"
}]
proc to_libre_date {args} {
package require punk::args
set argd [punk::args::parse $args withid ::winlibreoffice::to_libre_date]
lassign [dict values $argd] leaders opts values received
set tz [dict get $opts -timezone]
set time [dict get $values time]
if {![string is integer -strict $time]} {
set ts [clock scan $time -timezone $tz]
} else {
set ts $time
}
variable datebase
set tbase [clock scan $datebase -timezone $tz]
package require punk::timeinterval
set info [punk::timeinterval::difference -maxunit days -timezone $tz $tbase $ts]
lassign [dict values $info] _Y _m days h m s
return [expr {$days + ((($h *3600) + ($m * 60) + $s)/86400.0)}]
}
lappend PUNKARGS [list {
@id -id ::winlibreoffice::from_libre_date
@cmd -name winlibreoffice::from_libre_date -help\
"Convert an internal Libre Office date floating point value
representing the number of days since 1899-12-30 to a format
understood by Tcl such as 'clock seconds', 'clock milliseconds'
as specified in the -format option.
"
@opts
-format -default "clockseconds" -choices {clockseconds clockmillis ISO8601} -choicerestricted 0 -help\
"Aside from the special values listed -format accepts a format string
as accepted by the Tcl 'clock format' command's -format option."
-timezone -default "" -help\
"If unspecified, the timezone will be the
current time zone on the system"
@values -min 1 -max 1
libredatetime -type float -help\
"Floating point number representing the number of
days since 1899-12-30."
}]
#review - we don't expect sci notation for any float values here
#but we could easily get them.. e.g 0.000000001 * 86400.0 => 8.64e-5
#todo - clockmicros ?
proc from_libre_date {args} {
package require punk::args
set argd [punk::args::parse $args withid ::winlibreoffice::from_libre_date]
lassign [dict values $argd] leaders opts values received
set format [dict get $opts -format]
set tz [dict get $opts -timezone]
set libredatetime [dict get $values libredatetime]
variable datebase
set tbase [clock scan $datebase -timezone $tz]
set intdays [expr {int($libredatetime)}]
set fracdays [lindex [split $libredatetime .] 1]
if {$fracdays ne ""} {
set fracdays "0.$fracdays"
set floatsecs [expr {$fracdays * 86400.0}] ;#assuming not a leap-second day
if {$format eq "clockmillis"} {
set wholesecs [expr {int($floatsecs)}]
set msfrac [lindex [split $floatsecs .] 1]
if {$msfrac ne ""} {
set msfrac "0.$msfrac" ;#could also be something like 0.64e-5 which should still work
set ms [expr {round(1000 * $msfrac)}]
if {$ms == 1000} {
set ms 0
incr wholesecs
}
} else {
set ms 0
}
} else {
set wholesecs [expr {round($floatsecs)}]
set ms 0
}
} else {
set wholesecs 0
set ms 0
}
set cs [clock add $tbase +$intdays days +$wholesecs seconds -timezone $tz]
switch -- $format {
clockseconds {
return $cs
}
clockmillis {
return [expr {($cs * 1000) + $ms}]
}
ISO8601 {
set format "%Y%m%dT%H%M%S"
}
}
return [clock format $cs -format $format]
}
#time is represented on a scale of 0 to 1 6:00am = 0.25 (24/4)
#return libreoffice date as a floating point number of days since 1899.. (1899-12-30)
proc to_libre_date_from_clockseconds_gmt {cs} {
return [expr {($cs/86400.0) + 25569}]
}
#see also: https://wiki.tcl-lang.org/page/Tcom+examples+for+Microsoft+Outlook
# this also uses days since 1899 (but 31 dec?) and uses a fixed base_offset of 36526 (for 2000-01-01) - this might be a better approach than using punk::timeinterval anyway
# it seems to match libreoffice very closely (if not exact?) REVIEW
# wher val is days since 1899
proc msdate_to_iso {val} {
set base_ticks [clock scan 20000101]
set base_offset 36526;# days since 31. Dec 1899, ARRRGGHHHHH
set offset [expr {int($val)-$base_offset}]
set clkdate [clock scan "$offset days" -base $base_ticks]
set isodate [clock format $clkdate -format %Y%m%d]
set fhours [expr {24.0*($val-int($val))}]
set hours [expr {int($fhours)}]
set mins [expr {int(($fhours-$hours)*60)}]
#date<sp>H:m is valid iso but not if space replaced with T - then would need seconds too
return "${isodate} $hours:$mins"
}
}
# -----------------------------------------------------------------------------
# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked
# -----------------------------------------------------------------------------
# variable PUNKARGS
# variable PUNKARGS_aliases
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::winlibreoffice
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide winlibreoffice [namespace eval winlibreoffice {
variable version
set version 0.1.0
}]
return