# Enumerator of Words
# by Olivier R.
# License: MPL 2
import unohelper
import uno
import traceback
import platform
import helpers
import enum_strings
import grammalecte.graphspell as sc
from com.sun.star.task import XJobExecutor
from com.sun.star.awt import XActionListener
from com.sun.star.awt import XTopWindowListener
from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK
# BUTTONS_OK, BUTTONS_OK_CANCEL, BUTTONS_YES_NO, BUTTONS_YES_NO_CANCEL, BUTTONS_RETRY_CANCEL, BUTTONS_ABORT_IGNORE_RETRY
# DEFAULT_BUTTON_OK, DEFAULT_BUTTON_CANCEL, DEFAULT_BUTTON_RETRY, DEFAULT_BUTTON_YES, DEFAULT_BUTTON_NO, DEFAULT_BUTTON_IGNORE
from com.sun.star.awt.MessageBoxType import INFOBOX, ERRORBOX # MESSAGEBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
def MessageBox (xDocument, sMsg, sTitle, nBoxType=INFOBOX, nBoxButtons=BUTTONS_OK):
xParentWin = xDocument.CurrentController.Frame.ContainerWindow
ctx = uno.getComponentContext()
xToolkit = ctx.ServiceManager.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
xMsgBox = xToolkit.createMessageBox(xParentWin, nBoxType, nBoxButtons, sTitle, sMsg)
return xMsgBox.execute()
def hexToRBG (sHexa):
r = int(sHexa[:2], 16)
g = int(sHexa[2:4], 16)
b = int(sHexa[4:], 16)
return (r & 255) << 16 | (g & 255) << 8 | (b & 255)
def _waitPointer (funcDecorated):
def wrapper (*args, **kwargs):
# self is the first parameter if the decorator is applied on a object
self = args[0]
# before
xPointer = self.xSvMgr.createInstanceWithContext("com.sun.star.awt.Pointer", self.ctx)
xPointer.setType(uno.getConstantByName("com.sun.star.awt.SystemPointer.WAIT"))
xWindowPeer = self.xContainer.getPeer()
xWindowPeer.setPointer(xPointer)
for x in xWindowPeer.Windows:
x.setPointer(xPointer)
# processing
result = funcDecorated(*args, **kwargs)
# after
xPointer.setType(uno.getConstantByName("com.sun.star.awt.SystemPointer.ARROW"))
xWindowPeer.setPointer(xPointer)
for x in xWindowPeer.Windows:
x.setPointer(xPointer)
self.xContainer.setVisible(True) # seems necessary to refresh the dialog box and text widgets (why?)
# return
return result
return wrapper
class Enumerator (unohelper.Base, XActionListener, XTopWindowListener, XJobExecutor):
def __init__ (self, ctx):
self.ctx = ctx
self.xSvMgr = self.ctx.ServiceManager
self.xDesktop = self.xSvMgr.createInstanceWithContext("com.sun.star.frame.Desktop", self.ctx)
self.xDocument = self.xDesktop.getCurrentComponent()
self.xContainer = None
self.xDialog = None
self.oSpellChecker = None
self.oTokenizer = None
self.dWord = {}
def _addWidget (self, name, wtype, x, y, w, h, **kwargs):
xWidget = self.xDialog.createInstance('com.sun.star.awt.UnoControl%sModel' % wtype)
xWidget.Name = name
xWidget.PositionX = x
xWidget.PositionY = y
xWidget.Width = w
xWidget.Height = h
for k, w in kwargs.items():
setattr(xWidget, k, w)
self.xDialog.insertByName(name, xWidget)
return xWidget
def _addGrid (self, name, x, y, w, h, columns, **kwargs):
xGridModel = self.xDialog.createInstance('com.sun.star.awt.grid.UnoControlGridModel')
xGridModel.Name = name
xGridModel.PositionX = x
xGridModel.PositionY = y
xGridModel.Width = w
xGridModel.Height = h
xColumnModel = xGridModel.ColumnModel
for e in columns:
xCol = xColumnModel.createColumn()
for k, w in e.items():
setattr(xCol, k, w)
xColumnModel.addColumn(xCol)
for k, w in kwargs.items():
setattr(xGridModel, k, w)
self.xDialog.insertByName(name, xGridModel)
return xGridModel
def run (self, sLang):
self.dUI = enum_strings.getUI(sLang)
# dialog
self.xDialog = self.xSvMgr.createInstanceWithContext('com.sun.star.awt.UnoControlDialogModel', self.ctx)
self.xDialog.Width = 240
self.xDialog.Height = 280
self.xDialog.Title = self.dUI.get('title', "#title#")
xWindowSize = helpers.getWindowSize()
self.xDialog.PositionX = int((xWindowSize.Width / 2) - (self.xDialog.Width / 2))
self.xDialog.PositionY = int((xWindowSize.Height / 2) - (self.xDialog.Height / 2))
# fonts
xFDTitle = uno.createUnoStruct("com.sun.star.awt.FontDescriptor")
xFDTitle.Height = 9
xFDTitle.Weight = uno.getConstantByName("com.sun.star.awt.FontWeight.BOLD")
xFDTitle.Name = "Verdana"
xFDSubTitle = uno.createUnoStruct("com.sun.star.awt.FontDescriptor")
xFDSubTitle.Height = 8
xFDSubTitle.Weight = uno.getConstantByName("com.sun.star.awt.FontWeight.BOLD")
xFDSubTitle.Name = "Verdana"
# widget
nX = 10
nY1 = 5
nY2 = nY1 + 225
nWidth = self.xDialog.Width - 20
nHeight = 10
# List
self._addWidget("list_section", 'FixedLine', nX, nY1, nWidth, nHeight, Label = self.dUI.get("list_section", "#err"), FontDescriptor = xFDTitle)
self._addWidget('count_button', 'Button', nX, nY1+12, 70, 11, Label = self.dUI.get('count_button', "#err"))
self._addWidget('count2_button', 'Button', nX+75, nY1+12, 70, 11, Label = self.dUI.get('count2_button', "#err"))
self._addWidget('unknown_button', 'Button', nX+150, nY1+12, 70, 11, Label = self.dUI.get('unknown_button', "#err"))
self.xGridModel = self._addGrid("list_grid", nX, nY1+25, nWidth, 181, \
[ {"Title": self.dUI.get("words", "#err"), "ColumnWidth": 175}, {"Title": "Occurrences", "ColumnWidth": 45} ], \
SelectionModel = uno.Enum("com.sun.star.view.SelectionType", "MULTI") \
)
self._addWidget('num_of_entries', 'FixedText', nX, nY1+210, 30, nHeight, Label = self.dUI.get('num_of_entries', "#err"), Align = 2)
self.xNumWord = self._addWidget('num_of_entries_res', 'FixedText', nX+35, nY1+210, 25, nHeight, Label = "—")
self._addWidget('tot_of_entries', 'FixedText', nX+60, nY1+210, 30, nHeight, Label = self.dUI.get('tot_of_entries', "#err"), Align = 2)
self.xTotWord = self._addWidget('tot_of_entries_res', 'FixedText', nX+95, nY1+210, 30, nHeight, Label = "—")
self.xSearch = self._addWidget('search_button', 'Button', nX+145, nY1+210, 30, nHeight, Label = ">>>", HelpText=self.dUI.get('goto', "#err"), Enabled = False)
self.xExport = self._addWidget('export_button', 'Button', nX+180, nY1+210, 40, nHeight, Label = self.dUI.get('export', "#err"), Enabled = False)
# Tag
# Note: the only way to group RadioButtons is to create them successively
self._addWidget("charstyle_section", 'FixedLine', nX, nY2, 200, nHeight, Label = self.dUI.get("charstyle_section", "#err"), FontDescriptor = xFDTitle)
self.xAccent = self._addWidget('emphasis', 'RadioButton', nX, nY2+12, 55, nHeight, Label = self.dUI.get('emphasis', "#err"))
self.xStrongAccent = self._addWidget('strong_emphasis', 'RadioButton', nX+60, nY2+12, 70, nHeight, Label = self.dUI.get('strong_emphasis', "#err"))
self.xNoAccent = self._addWidget('nostyle', 'RadioButton', nX+140, nY2+12, 45, nHeight, Label = self.dUI.get('nostyle', "#err"))
self.xTag = self._addWidget('tag_button', 'Button', self.xDialog.Width-40, nY2+10, 30, 11, Label = self.dUI.get('tag_button', "#err"), FontDescriptor = xFDTitle, TextColor = 0x005500, Enabled=False)
# Progress bar
self.xProgressBar = self._addWidget('progress_bar', 'ProgressBar', nX, self.xDialog.Height-25, 160, 14)
self.xProgressBar.ProgressValueMin = 0
self.xProgressBar.ProgressValueMax = 1 # to calculate later
# Close
self._addWidget('close_button', 'Button', self.xDialog.Width-60, self.xDialog.Height-25, 50, 14, Label = self.dUI.get('close_button', "#err"), FontDescriptor = xFDTitle, TextColor = 0x550000)
# container
self.xContainer = self.xSvMgr.createInstanceWithContext('com.sun.star.awt.UnoControlDialog', self.ctx)
self.xContainer.setModel(self.xDialog)
self.xGridControl = self.xContainer.getControl('list_grid')
self.xContainer.getControl('count_button').addActionListener(self)
self.xContainer.getControl('count_button').setActionCommand('Count')
self.xContainer.getControl('count2_button').addActionListener(self)
self.xContainer.getControl('count2_button').setActionCommand('CountByLemma')
self.xContainer.getControl('unknown_button').addActionListener(self)
self.xContainer.getControl('unknown_button').setActionCommand('UnknownWords')
self.xContainer.getControl('search_button').addActionListener(self)
self.xContainer.getControl('search_button').setActionCommand('Search')
self.xContainer.getControl('export_button').addActionListener(self)
self.xContainer.getControl('export_button').setActionCommand('Export')
self.xContainer.getControl('tag_button').addActionListener(self)
self.xContainer.getControl('tag_button').setActionCommand('Tag')
self.xContainer.getControl('close_button').addActionListener(self)
self.xContainer.getControl('close_button').setActionCommand('Close')
self.xContainer.addTopWindowListener(self) # listener with XTopWindowListener methods
self.xContainer.setVisible(True) # True for non modal dialog
xToolkit = self.xSvMgr.createInstanceWithContext('com.sun.star.awt.ExtToolkit', self.ctx)
self.xContainer.createPeer(xToolkit, None)
#self.xContainer.execute() # Don’t excute for non modal dialog
# XActionListener
def actionPerformed (self, xActionEvent):
try:
if xActionEvent.ActionCommand == "Count":
self.count(self.dUI.get("words", "#err"))
self.xTag.Enabled = True
self.xSearch.Enabled = True
self.xExport.Enabled = True
elif xActionEvent.ActionCommand == "CountByLemma":
self.count(self.dUI.get("lemmas", "#err"), bByLemma=True)
self.xTag.Enabled = False
self.xSearch.Enabled = False
self.xExport.Enabled = True
elif xActionEvent.ActionCommand == "UnknownWords":
self.count(self.dUI.get("unknown_words", "#err"), bOnlyUnknownWords=True)
self.xTag.Enabled = True
self.xSearch.Enabled = True
self.xExport.Enabled = True
elif xActionEvent.ActionCommand == "Search":
if not self.xGridControl.hasSelectedRows():
return
lRow = self.xGridControl.getSelectedRows()
aWord = set([ self.xGridModel.GridDataModel.getCellData(0, n) for n in lRow ])
self.gotoWord(aWord)
elif xActionEvent.ActionCommand == "Export":
self.export()
elif xActionEvent.ActionCommand == "Tag":
if not self.xGridControl.hasSelectedRows():
return
lRow = self.xGridControl.getSelectedRows()
aWord = set([ self.xGridModel.GridDataModel.getCellData(0, n) for n in lRow ])
sAction = ""
if self.xAccent.State:
sAction = "emphasis"
elif self.xStrongAccent.State:
sAction = "strong_emphasis"
elif self.xNoAccent.State:
sAction = "nostyle"
self.tagText(aWord, sAction)
elif xActionEvent.ActionCommand == "Close":
self.xContainer.dispose() # Non modal dialog
#self.xContainer.endExecute() # Modal dialog
except:
traceback.print_exc()
# XTopWindowListener (useful for non modal dialog only)
def windowOpened (self, xEvent):
return
def windowClosing (self, xEvent):
self.xContainer.dispose() # Non modal dialog
def windowClosed (self, xEvent):
return
def windowMinimized (self, xEvent):
return
def windowNormalized (self, xEvent):
return
def windowActivated (self, xEvent):
return
def windowDeactivated (self, xEvent):
return
# XJobExecutor
def trigger (self, args):
try:
xDialog = Enumerator(self.ctx)
xDialog.run()
except:
traceback.print_exc()
# Code
def _setTitleOfFirstColumn (self, sTitle):
xColumnModel = self.xGridModel.ColumnModel
xColumn = xColumnModel.getColumn(0)
xColumn.Title = sTitle
def _getParagraphsFromText (self):
"generator: returns full document text paragraph by paragraph"
xCursor = self.xDocument.Text.createTextCursor()
xCursor.gotoStart(False)
xCursor.gotoEndOfParagraph(True)
yield xCursor.getString()
while xCursor.gotoNextParagraph(False):
xCursor.gotoEndOfParagraph(True)
yield xCursor.getString()
def _countParagraph (self):
i = 1
xCursor = self.xDocument.Text.createTextCursor()
xCursor.gotoStart(False)
while xCursor.gotoNextParagraph(False):
i += 1
return i
@_waitPointer
def count (self, sTitle, bByLemma=False, bOnlyUnknownWords=False):
if not self.oSpellChecker:
self.oSpellChecker = sc.SpellChecker("fr")
self._setTitleOfFirstColumn(sTitle)
self.xProgressBar.ProgressValueMax = self._countParagraph() * 2
self.xProgressBar.ProgressValue = 0
xGridDataModel = self.xGridModel.GridDataModel
xGridDataModel.removeAllRows()
self.dWord.clear()
for sParagraph in self._getParagraphsFromText():
self.dWord = self.oSpellChecker.countWordsOccurrences(sParagraph, bByLemma, bOnlyUnknownWords, self.dWord)
self.xProgressBar.ProgressValue += 1
self.xProgressBar.ProgressValueMax += len(self.dWord)
i = 0
nTotOccur = 0
for sWord, nOccur in sorted(self.dWord.items(), key=lambda t: t[1], reverse=True):
xGridDataModel.addRow(i, (sWord, nOccur))
self.xProgressBar.ProgressValue += 1
i += 1
nTotOccur += nOccur
sWord = sWord.lower()
if sWord.endswith(("-le-moi", "-le-toi", "-le-nous", "-le-vous", "le-lui", "-le-leur", \
"-la-moi", "-la-toi", "-la-nous", "-la-vous", "la-lui", "-la-leur", \
"-les-moi", "-les-toi", "-les-nous", "-les-vous", "les-lui", "-les-leur", \
"-m’en", "-t’en", "-lui-en", "-nous-en", "vous-en", "-leur-en")):
nTotOccur += nOccur + nOccur
elif sWord.endswith(("-je", "-tu", "-il", "-elle", "-on", "-nous", "-vous", "-ils", "-elles", "-iel", "-iels", "-le", \
"-la", "-les", "-moi", "-toi", "-leur", "-en", "-y")):
nTotOccur += nOccur
self.xProgressBar.ProgressValue = self.xProgressBar.ProgressValueMax
self.xNumWord.Label = str(i)
self.xTotWord.Label = nTotOccur
def export (self):
if not self.dWord:
return
sText = ""
for sWord, nOccur in sorted(self.dWord.items(), key=lambda t: t[1], reverse=True):
sText += sWord + "\t" + str(nOccur) + "\n"
try:
xFilePicker = self.xSvMgr.createInstanceWithContext('com.sun.star.ui.dialogs.FilePicker', self.ctx) # other possibility: com.sun.star.ui.dialogs.SystemFilePicker
xFilePicker.initialize([uno.getConstantByName("com.sun.star.ui.dialogs.TemplateDescription.FILESAVE_SIMPLE")]) # seems useless
xFilePicker.appendFilter("Supported files", "*.txt")
xFilePicker.setDefaultName("word_count.txt") # doesn’t work on Windows
xFilePicker.setDisplayDirectory("")
xFilePicker.setMultiSelectionMode(False)
nResult = xFilePicker.execute()
if nResult == 1:
# lFile = xFilePicker.getSelectedFiles()
lFile = xFilePicker.getFiles()
spfExported = lFile[0][5:].lstrip("/") # remove file://
if platform.system() != "Windows":
spfExported = "/" + spfExported
#spfExported = os.path.join(os.path.expanduser("~"), "fr.personal.json")
with open(spfExported, "w", encoding="utf-8") as hDst:
hDst.write(sText)
except:
sMessage = traceback.format_exc()
MessageBox(self.xDocument, sMessage, self.dUI.get('export_title', "#err"), ERRORBOX)
@_waitPointer
def tagText (self, aWord, sAction=""):
if not sAction:
return
self.xProgressBar.ProgressValueMax = self._countParagraph()
self.xProgressBar.ProgressValue = 0
if not self.oTokenizer:
self.oTokenizer = self.oSpellChecker.getTokenizer()
xCursor = self.xDocument.Text.createTextCursor()
xCursor.gotoStart(False)
self._tagParagraph(xCursor, aWord, sAction)
while xCursor.gotoNextParagraph(False):
self._tagParagraph(xCursor, aWord, sAction)
self.xProgressBar.ProgressValue = self.xProgressBar.ProgressValueMax
def _tagParagraph (self, xCursor, aWord, sAction):
xCursor.gotoEndOfParagraph(True)
sParagraph = xCursor.getString()
xCursor.gotoStartOfParagraph(False)
nPos = 0
for dToken in self.oTokenizer.genTokens(sParagraph):
if dToken["sValue"] in aWord:
xCursor.goRight(dToken["nStart"]-nPos, False) # start of token
nPos = dToken["nEnd"]
xCursor.goRight(nPos-dToken["nStart"], True) # end of token
# if sAction == "underline":
# xCursor.CharBackColor = hexToRBG("AA0000")
# elif sAction == "nounderline":
# xCursor.CharBackColor = hexToRBG("FFFFFF")
if sAction == "emphasis":
xCursor.CharStyleName = "Emphasis"
elif sAction == "strong_emphasis":
xCursor.CharStyleName = "Strong Emphasis"
elif sAction == "nostyle":
#xCursor.CharStyleName = "Default Style" # doesn’t work
xCursor.setPropertyToDefault("CharStyleName")
self.xProgressBar.ProgressValue += 1
@_waitPointer
def gotoWord (self, aWord):
try:
if not aWord:
return
if not self.oTokenizer:
self.oTokenizer = self.oSpellChecker.getTokenizer()
xViewCursor = self.xDocument.CurrentController.ViewCursor
if not xViewCursor.isCollapsed():
xViewCursor.collapseToEnd()
xRange = xViewCursor.getStart()
xCursor = self.xDocument.Text.createTextCursor()
xCursor.gotoRange(xRange, False)
#xCursor.gotoEndOfWord(False)
xCursor.gotoEndOfParagraph(True)
sParagraph = xCursor.getString()
xCursor.gotoRange(xRange, False)
tPos = self._searchWordsInText(aWord, sParagraph)
if not tPos:
while xCursor.gotoNextParagraph(False):
xCursor.gotoEndOfParagraph(True)
sParagraph = xCursor.getString()
xCursor.gotoStartOfParagraph(False)
tPos = self._searchWordsInText(aWord, sParagraph)
if tPos:
break
if not tPos:
xCursor.gotoStart(False)
xCursor.gotoEndOfParagraph(True)
sParagraph = xCursor.getString()
xCursor.gotoStartOfParagraph(True)
tPos = self._searchWordsInText(aWord, sParagraph)
while xCursor.gotoNextParagraph(False):
xCursor.gotoEndOfParagraph(True)
sParagraph = xCursor.getString()
xCursor.gotoStartOfParagraph(False)
tPos = self._searchWordsInText(aWord, sParagraph)
if tPos:
break
if tPos:
xCursor.goRight(tPos[0], False)
xRange = xCursor.getStart()
xViewCursor.gotoRange(xRange, False)
xViewCursor.goRight(tPos[1], True)
except:
traceback.print_exc()
def _searchWordsInText (self, aWord, sText):
for dToken in self.oTokenizer.genTokens(sText):
if dToken["sValue"] in aWord:
return (dToken["nStart"], dToken["nEnd"]-dToken["nStart"])
return None
#g_ImplementationHelper = unohelper.ImplementationHelper()
#g_ImplementationHelper.addImplementation(Enumerator, 'net.grammalecte.enumerator', ('com.sun.star.task.Job',))