# -*- tcl -*- # Maintenance Instruction: leave the 999999.xxx.x as is and use 'dev make' or src/make.tcl to update from -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 # @@ 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)}] #dateH: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