52080f3101306fd1433c82de5b2d725654ab040c
[superbchemistry/superbchemistry.git] / extension / SuperbChemistry / Main.xba
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
3 <script:module xmlns:script="http://openoffice.org/2000/script" script:name="Main" script:language="StarBasic">Option Explicit
4
5 Function HaveSelection(doc As Object)
6         Dim sel as Object
7         sel = doc.CurrentController.Selection
8         If sel.Count &gt; 1 Then
9                 HaveSelection = True
10                 Exit Function
11         End If
12         Dim s0
13         s0 = sel.getByIndex(0)
14         If s0.Text.compareRegionStarts(s0.Start, s0.End) = 0 Then
15                 HaveSelection = False
16         Else
17                 HaveSelection = True
18         End If
19 End Function
20
21 &apos; Regular expression replace in the document, creating superscripts if
22 &apos; superb &gt; 0 or subscripts if superb &lt; 0.
23 Sub SuperbReplace(doc As Object, searchRegex As String, replacePattern As String, superb As Integer)
24
25         Dim rd As Object
26         rd = doc.createReplaceDescriptor()
27
28         rd.SearchCaseSensitive = true
29         rd.SearchRegularExpression = true
30         rd.setSearchString(searchRegex)
31         rd.setReplaceString(replacePattern)
32
33         If superb &lt;&gt; 0 Then
34                 Dim replaceAttrs(1) As New com.sun.star.beans.PropertyValue
35                 replaceAttrs(0).Name = &quot;CharEscapement&quot;
36                 If superb &gt; 0 Then
37                         replaceAttrs(0).Value = 33
38                 Else
39                         &apos; The default escapement for subscripts is -33, which looked bad to me in
40                         &apos; chemical formulas.  This looks better.  Modify to your taste.
41                         replaceAttrs(0).Value = -9
42                 End If
43                 replaceAttrs(1).Name = &quot;CharEscapementHeight&quot;
44                 replaceAttrs(1).Value = 58
45                 rd.setReplaceAttributes(replaceAttrs)
46         End If
47
48         doc.replaceAll(rd)
49
50 End Sub
51
52 Sub ReplaceInSelection(doc As Object, searchRegex As String, replacePattern As String)
53
54         Dim frame As Object, dispatcher As Object
55         frame = doc.CurrentController.Frame
56         dispatcher = createUnoService(&quot;com.sun.star.frame.DispatchHelper&quot;)
57
58         Dim args(6) As New com.sun.star.beans.PropertyValue
59         args(0).Name = &quot;SearchItem.AlgorithmType&quot;
60         args(0).Value = 1
61         args(1).Name = &quot;SearchItem.SearchFlags&quot;
62         args(1).Value = &amp;H1800  &apos; Search in selection
63         args(2).Name = &quot;SearchItem.SearchString&quot;
64         args(2).Value = searchRegex
65         args(3).Name = &quot;SearchItem.ReplaceString&quot;
66         args(3).Value = replacePattern
67         args(4).Name = &quot;SearchItem.Command&quot;
68         args(4).Value = 3
69         args(5).Name = &quot;SearchItem.AlgorithmType2&quot;
70         args(5).Value = 2
71         args(6).Name = &quot;Quiet&quot;
72         args(6).Value = true
73
74         dispatcher.executeDispatch(frame, &quot;.uno:ExecuteSearch&quot;, &quot;&quot;, 0, args())
75
76 End Sub
77
78 Global formatSelectionWarningShown As Boolean
79 &apos; I haven&apos;t found a way to initialize the variable, but it looks like its
80 &apos; default value is treated as false in an if statement.
81 &apos;formatSelectionWarningShown = False
82
83 Sub FormatSelectionOrDocumentDebug()
84         &apos; Replacing with an empty replacement string triggers a bug in LibreOffice
85         &apos; (https://bugs.documentfoundation.org/show_bug.cgi?id=136577), so we must
86         &apos; avoid it.  Fortunately, avoiding it is pretty straightforward.
87
88         &apos; Step 1: Insert @m@ between an item and a number or charge that may be part of
89         &apos; a chemical formula (subject to later checks).
90         If HaveSelection(ThisComponent) Then
91                 &apos; doc.replaceAll is not capable of searching a selection, while the
92                 &apos; dispatch-based replace API uses the current format options in the
93                 &apos; &quot;Find &amp; Replace&quot; dialog and does not let us change them, which means that
94                 &apos; (1) existing settings can break things and (2) we cannot make superscripts
95                 &apos; and subscripts.  We do the best we can by using one dispatch-based replace
96                 &apos; to tag all the sequences we want to operate on (which loses the selection in
97                 &apos; OpenOffice anyway) and then proceed with replaceAll calls on the whole
98                 &apos; document, which will only operate on the already tagged sequences.
99                 If Not formatSelectionWarningShown Then
100                         MsgBox &quot;Due to limitations in the OpenOffice/LibreOffice API, the &quot; &amp; _
101                                 &quot;&quot;&quot;Format selection&quot;&quot; command may not process some chemical formulas &quot; &amp; _
102                                 &quot;in the selection or may apply incorrect formatting if any format &quot; &amp; _
103                                 &quot;options are active in the &quot;&quot;Find &amp; Replace&quot;&quot; dialog box.  If this &quot; &amp; _
104                                 &quot;happens, just undo the command (if any changes were made), clear the &quot; &amp; _
105                                 &quot;format options in the &quot;&quot;Find &amp; Replace&quot;&quot; dialog box (focus the &quot; &amp; _
106                                 &quot;&quot;&quot;Find&quot;&quot; field, click &quot;&quot;No Format&quot;&quot;, and repeat for the &quot;&quot;Replace&quot;&quot; &quot; &amp; _
107                                 &quot;field), and run &quot;&quot;Format selection&quot;&quot; again.&quot; &amp; Chr$(13) &amp; Chr$(13) &amp; _
108                                 &quot;This message is always shown on the first &quot;&quot;Format selection&quot;&quot; &quot; &amp; _
109                                 &quot;command in each OpenOffice/LibreOffice session because &quot; &amp; _
110                                 &quot;SuperbChemistry has no way to detect whether format options are &quot; &amp; _
111                                 &quot;active in &quot;&quot;Find &amp; Replace&quot;&quot;.&quot;, _
112                                 0, &quot;SuperbChemistry &quot;&quot;Format selection&quot;&quot; notice&quot;
113                         formatSelectionWarningShown = True
114                 End If
115                 ReplaceInSelection(ThisComponent, &quot;(?&lt;=[A-Z][a-z]?|[\])}])[-+−0-9]+&quot;, &quot;@m@&amp;&quot;)
116         Else
117                 SuperbReplace(ThisComponent, &quot;(?&lt;=[A-Z][a-z]?|[\])}])[-+−0-9]+&quot;, &quot;@m@&amp;&quot;, 0)
118         End If
119
120         &apos; Step 2: Insert @c@ after a charge symbol, if it&apos;s followed by one of the
121         &apos; allowed characters for the second kind of &quot;recognized sequence&quot; described in
122         &apos; the readme.
123         SuperbReplace(ThisComponent, &quot;(?&lt;=@m@)([0-9]*[-+−])(?=[ \t\])}.,:;?!&apos;&quot;&quot;]|$)&quot;, &quot;&amp;@c@&quot;, 0)
124
125         &apos; Step 3: Real minus signs in charges.
126         SuperbReplace(ThisComponent, &quot;-@c@&quot;, &quot;−@c@&quot;, 0)
127
128         &apos; Step 4: Some groups grab a single following digit as a quantity rather than a
129         &apos; charge amount.  Insert @sq@ marker to prevent the charge from grabbing the
130         &apos; digit.
131         SuperbReplace(ThisComponent, &quot;(?&lt;=(H|O|F|Cl|Br|I|[\])}])@m@)[0-9]&quot;, &quot;&amp;@sq@&quot;, 0)
132
133         &apos; Step 5: Each charge grabs at most one digit and moves the @c@ in front to
134         &apos; prevent the quantity from grabbing the digit.
135         SuperbReplace(ThisComponent, &quot;([0-9]?[−+])@c@&quot;, &quot;@c@$1&quot;, 1)
136
137         &apos; Step 6: Remove any @sq@ markers so items can grab all the digits that follow
138         &apos; for the quantity.
139         SuperbReplace(ThisComponent, &quot;(.)@sq@&quot;, &quot;$1&quot;, 0)
140
141         &apos; At this point, we have only @m@ and @c@ markers left.
142
143         &apos; Step 7: Format quantities: as many digits as we can still grab.  The digits
144         &apos; must be followed by one of the allowed characters for the first kind of
145         &apos; &quot;recognized sequence&quot; described by a readme or by @, which we assume is part
146         &apos; of a @c@ tag we added in step 2.  The allowed characters A-Z\[({ represent
147         &apos; the beginning of another item; the other allowed characters are the same as
148         &apos; in step 2.
149         SuperbReplace(ThisComponent, &quot;(?&lt;=@m@)[0-9]+(?=[@A-Z\[({ \t\])}.,:;?!&apos;&quot;&quot;]|$)&quot;, &quot;&amp;&quot;, -1)
150
151         &apos; Step 8: Clean up @c@ markers.  We know there is a charge sign after each.
152         SuperbReplace(ThisComponent, &quot;@c@(.)&quot;, &quot;$1&quot;, 0)
153
154         &apos; Step 9: Clean up @m@ markers.  We know there is some character before each.
155         SuperbReplace(ThisComponent, &quot;(.)@m@&quot;, &quot;$1&quot;, 0)
156
157 End Sub
158
159 Dim madeChanges As Boolean
160
161 Sub UndoListener_undoActionAdded()
162 End Sub
163 Sub UndoListener_actionUndone()
164 End Sub
165 Sub UndoListener_actionRedone()
166 End Sub
167 Sub UndoListener_allActionsCleared()
168 End Sub
169 Sub UndoListener_redoActionsCleared()
170 End Sub
171 Sub UndoListener_resetAll()
172 End Sub
173 Sub UndoListener_enteredContext()
174 End Sub
175 Sub UndoListener_enteredHiddenContext()
176 End Sub
177 Sub UndoListener_leftContext()
178         madeChanges = True
179 End Sub
180 Sub UndoListener_leftHiddenContext()
181 End Sub
182 Sub UndoListener_cancelledContext()
183 End Sub
184
185 Sub FormatSelectionOrDocument()
186
187         Dim undoActionName As String
188         If HaveSelection(ThisComponent) Then
189                 undoActionName = &quot;SuperbChemistry: Format selection&quot;
190         Else
191                 undoActionName = &quot;SuperbChemistry: Format document&quot;
192         End If
193         ThisComponent.UndoManager.enterUndoContext(undoActionName)
194
195         On Error Goto ErrorHandler
196         FormatSelectionOrDocumentDebug
197         On Error Goto 0
198
199         ThisComponent.UndoManager.leaveUndoContext()
200         Exit Sub
201
202 ErrorHandler:
203
204         &apos; If our undo context is nonempty, we want to undo the generated action.
205         &apos; If not, we do not want to undo as that would undo the user&apos;s previous
206         &apos; action.  If we just check whether the title of the last undoable action
207         &apos; is &quot;SuperbChemistry: Format (selection|document)&quot;, that might be wrong if
208         &apos; the user ran FormatDocument twice in a row: probably unlikely, but the
209         &apos; completely correct check is not that hard.
210         Dim listener As Object
211         listener = CreateUnoListener(&quot;UndoListener_&quot;, &quot;com.sun.star.document.XUndoManagerListener&quot;)
212         madeChanges = False
213         ThisComponent.UndoManager.addUndoManagerListener(listener)
214         ThisComponent.UndoManager.leaveUndoContext()
215         ThisComponent.UndoManager.removeUndoManagerListener(listener)
216         If madeChanges Then
217                 ThisComponent.UndoManager.undo()
218                 ThisComponent.UndoManager.clearRedo()
219         End If
220
221         MsgBox &quot;SuperbChemistry encountered an unexpected error:&quot; &amp; Chr$(13) &amp; Chr$(13) &amp; _
222                 &quot;Code &quot; &amp; Err &amp; &quot;: &quot; &amp; Error$ &amp; Chr$(13) &amp; Chr$(13) &amp; _
223                 &quot;Any changes made so far have been undone.&quot; &amp; Chr$(13) &amp; Chr$(13) &amp; _
224                 &quot;SuperbChemistry needed to catch the error in order to leave your undo &quot; &amp; _
225                 &quot;history in a consistent state.  If the problem is reproducible and you &quot; &amp; _
226                 &quot;want to see the precise error location, run the FormatSelectionOrDocumentDebug &quot; &amp; _
227                 &quot;macro, but be advised that it may generate multiple entries in the undo &quot; &amp; _
228                 &quot;history and will not undo them on error.&quot;, _
229                 0, &quot;SuperbChemistry internal error&quot;
230         On Error Goto 0
231 End Sub
232
233 </script:module>