About Tesseract OCR.


Javi Domínguez
 

Hello.

I like the addon but it has some bugs that cause erratic behavior.

When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file not supported" (tested with PDF and BMP file types). The same file recognized from a folder in Windows explorer works fine.

2. If another file has been recognized before, it process any file even if it is not of a supported type. In any case, supported type or not, it always shows the result of the previous recognition, not the requested file. Even after manually deleting the oc.txt and ocr-xxx.png files from the addon's images folder, it re-processes the previously requested file.

None of the above shows any messages or errors in the log.

On the other hand, recognition from scanner does not work for me. My scanner is HP Scanjet G2410, It may not be supported but the addon does not speak any message about it. The thread that processes this remains active and never terminates. If the script is executed again, another thread is launched that also remains active and so on forever. It is normal for the user, if he does not receive a response, to try to run it again, so you can end up with a lot of active threads.

I have the habit of naming the treads that I use to be able to debug better with threading.enumerate(). I have added the line to __init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.

finally, in terms of user experience, I think you need to give more information about what is happening. Sometimes, if the recognition takes time, the user does not know if it is working correctly or not.


Greetings.


Javi Dominguez


Rui Fontes
 

Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file not supported" (tested with PDF and BMP file types). The same file recognized from a folder in Windows explorer works fine.

* It is a limitation of the routine to get the complete path of the file...

If you can help bettering the routine, I will be glad!



2. If another file has been recognized before, it process any file even if it is not of a supported type. In any case, supported type or not, it always shows the result of the previous recognition, not the requested file. Even after manually deleting the oc.txt and ocr-xxx.png files from the addon's images folder, it re-processes the previously requested file.

* It was an error on code... The path of last document was not cleared, so list of ocr-xxx.png file was created again...



On the other hand, recognition from scanner does not work for me. My scanner is HP Scanjet G2410, It may not be supported but the addon does not speak any message about it. The thread that processes this remains active and never terminates. If the script is executed again, another thread is launched that also remains active and so on forever. It is normal for the user, if he does not receive a response, to try to run it again, so you can end up with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to debug better with threading.enumerate(). I have added the line to __init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active, right?


finally, in terms of user experience, I think you need to give more information about what is happening. Sometimes, if the recognition takes time, the user does not know if it is working correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes


Javi Domínguez
 

Hello.


* It is a limitation of the routine to get the complete path of the file...

If you can help bettering the routine, I will be glad!

OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?

what do you mean with "is recognized as WIA compatible"? Yes, the scanner is WIA compliant, other apps recognize it but the addon I don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...

Yes, it would be convenient.


* And, before starting another thread, verify if it is active, right?

I think so. I would wait for the current thread to finish before starting another.


Note that if you assign the new thread to self._thread, the old thread will continue to run until it finishes but you will no longer have a reference to it. You will only be able to access it via threading.enumerate().


You may need a method to kill threads that are stuck or taking too long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file not supported" (tested with PDF and BMP file types). The same file recognized from a folder in Windows explorer works fine.

* It is a limitation of the routine to get the complete path of the file...

If you can help bettering the routine, I will be glad!



2. If another file has been recognized before, it process any file even if it is not of a supported type. In any case, supported type or not, it always shows the result of the previous recognition, not the requested file. Even after manually deleting the oc.txt and ocr-xxx.png files from the addon's images folder, it re-processes the previously requested file.

* It was an error on code... The path of last document was not cleared, so list of ocr-xxx.png file was created again...



On the other hand, recognition from scanner does not work for me. My scanner is HP Scanjet G2410, It may not be supported but the addon does not speak any message about it. The thread that processes this remains active and never terminates. If the script is executed again, another thread is launched that also remains active and so on forever. It is normal for the user, if he does not receive a response, to try to run it again, so you can end up with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to debug better with threading.enumerate(). I have added the line to __init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active, right?


finally, in terms of user experience, I think you need to give more information about what is happening. Sometimes, if the recognition takes time, the user does not know if it is working correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes






Javi Domínguez
 

Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in the Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research. Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of the file...

If you can help bettering the routine, I will be glad!

OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?

what do you mean with "is recognized as WIA compatible"? Yes, the scanner is WIA compliant, other apps recognize it but the addon I don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...

Yes, it would be convenient.


* And, before starting another thread, verify if it is active, right?

I think so. I would wait for the current thread to finish before starting another.


Note that if you assign the new thread to self._thread, the old thread will continue to run until it finishes but you will no longer have a reference to it. You will only be able to access it via threading.enumerate().


You may need a method to kill threads that are stuck or taking too long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file not supported" (tested with PDF and BMP file types). The same file recognized from a folder in Windows explorer works fine.

* It is a limitation of the routine to get the complete path of the file...

If you can help bettering the routine, I will be glad!



2. If another file has been recognized before, it process any file even if it is not of a supported type. In any case, supported type or not, it always shows the result of the previous recognition, not the requested file. Even after manually deleting the oc.txt and ocr-xxx.png files from the addon's images folder, it re-processes the previously requested file.

* It was an error on code... The path of last document was not cleared, so list of ocr-xxx.png file was created again...



On the other hand, recognition from scanner does not work for me. My scanner is HP Scanjet G2410, It may not be supported but the addon does not speak any message about it. The thread that processes this remains active and never terminates. If the script is executed again, another thread is launched that also remains active and so on forever. It is normal for the user, if he does not receive a response, to try to run it again, so you can end up with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to debug better with threading.enumerate(). I have added the line to __init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active, right?


finally, in terms of user experience, I think you need to give more information about what is happening. Sometimes, if the recognition takes time, the user does not know if it is working correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes









Javi Domínguez
 

Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez

El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in the Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research. Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of the file...

If you can help bettering the routine, I will be glad!

OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?

what do you mean with "is recognized as WIA compatible"? Yes, the scanner is WIA compliant, other apps recognize it but the addon I don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...

Yes, it would be convenient.


* And, before starting another thread, verify if it is active, right?

I think so. I would wait for the current thread to finish before starting another.


Note that if you assign the new thread to self._thread, the old thread will continue to run until it finishes but you will no longer have a reference to it. You will only be able to access it via threading.enumerate().


You may need a method to kill threads that are stuck or taking too long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file not supported" (tested with PDF and BMP file types). The same file recognized from a folder in Windows explorer works fine.

* It is a limitation of the routine to get the complete path of the file...

If you can help bettering the routine, I will be glad!



2. If another file has been recognized before, it process any file even if it is not of a supported type. In any case, supported type or not, it always shows the result of the previous recognition, not the requested file. Even after manually deleting the oc.txt and ocr-xxx.png files from the addon's images folder, it re-processes the previously requested file.

* It was an error on code... The path of last document was not cleared, so list of ocr-xxx.png file was created again...



On the other hand, recognition from scanner does not work for me. My scanner is HP Scanjet G2410, It may not be supported but the addon does not speak any message about it. The thread that processes this remains active and never terminates. If the script is executed again, another thread is launched that also remains active and so on forever. It is normal for the user, if he does not receive a response, to try to run it again, so you can end up with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to debug better with threading.enumerate(). I have added the line to __init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active, right?


finally, in terms of user experience, I think you need to give more information about what is happening. Sometimes, if the recognition takes time, the user does not know if it is working correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes












Rui Fontes
 

Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have selected to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on, I didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:

Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in the Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research. Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of the file...

If you can help bettering the routine, I will be glad!

OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?

what do you mean with "is recognized as WIA compatible"? Yes, the scanner is WIA compliant, other apps recognize it but the addon I don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...

Yes, it would be convenient.


* And, before starting another thread, verify if it is active, right?

I think so. I would wait for the current thread to finish before starting another.


Note that if you assign the new thread to self._thread, the old thread will continue to run until it finishes but you will no longer have a reference to it. You will only be able to access it via threading.enumerate().


You may need a method to kill threads that are stuck or taking too long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file not supported" (tested with PDF and BMP file types). The same file recognized from a folder in Windows explorer works fine.

* It is a limitation of the routine to get the complete path of the file...

If you can help bettering the routine, I will be glad!



2. If another file has been recognized before, it process any file even if it is not of a supported type. In any case, supported type or not, it always shows the result of the previous recognition, not the requested file. Even after manually deleting the oc.txt and ocr-xxx.png files from the addon's images folder, it re-processes the previously requested file.

* It was an error on code... The path of last document was not cleared, so list of ocr-xxx.png file was created again...



On the other hand, recognition from scanner does not work for me. My scanner is HP Scanjet G2410, It may not be supported but the addon does not speak any message about it. The thread that processes this remains active and never terminates. If the script is executed again, another thread is launched that also remains active and so on forever. It is normal for the user, if he does not receive a response, to try to run it again, so you can end up with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to debug better with threading.enumerate(). I have added the line to __init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active, right?


finally, in terms of user experience, I think you need to give more information about what is happening. Sometimes, if the recognition takes time, the user does not know if it is working correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes















Beqa Gozalishvili
 

hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which locks up NVDA.
If user have bad internet connection, they can stay without speech for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.

On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on, I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of the
file...

If you can help bettering the routine, I will be glad!

OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?

what do you mean with "is recognized as WIA compatible"? Yes, the
scanner is WIA compliant, other apps recognize it but the addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...

Yes, it would be convenient.


* And, before starting another thread, verify if it is active, right?

I think so. I would wait for the current thread to finish before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file
not supported" (tested with PDF and BMP file types). The same file
recognized from a folder in Windows explorer works fine.

* It is a limitation of the routine to get the complete path of the
file...

If you can help bettering the routine, I will be glad!



2. If another file has been recognized before, it process any file
even if it is not of a supported type. In any case, supported type
or not, it always shows the result of the previous recognition,
not the requested file. Even after manually deleting the oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.

* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...



On the other hand, recognition from scanner does not work for me.
My scanner is HP Scanjet G2410, It may not be supported but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the script
is executed again, another thread is launched that also remains
active and so on forever. It is normal for the user, if he does
not receive a response, to try to run it again, so you can end up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to
debug better with threading.enumerate(). I have added the line to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active, right?


finally, in terms of user experience, I think you need to give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes



















--
with best regards Beqa Gozalishvili
Tell: +995593454005
Email: beqaprogger@...
Web: https://gozaltech.org
Skype: beqabeqa473
Telegram: https://t.me/gozaltech
facebook: https://facebook.com/gozaltech
twitter: https://twitter.com/beqabeqa473
Instagram: https://instagram.com/beqa.gozalishvili


Rui Fontes
 

Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:

hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which locks up NVDA.
If user have bad internet connection, they can stay without speech for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on, I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"? Yes, the
scanner is WIA compliant, other apps recognize it but the addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active, right?
I think so. I would wait for the current thread to finish before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file
not supported" (tested with PDF and BMP file types). The same file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path of the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any file
even if it is not of a supported type. In any case, supported type
or not, it always shows the result of the previous recognition,
not the requested file. Even after manually deleting the oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...


On the other hand, recognition from scanner does not work for me.
My scanner is HP Scanjet G2410, It may not be supported but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the script
is executed again, another thread is launched that also remains
active and so on forever. It is normal for the user, if he does
not receive a response, to try to run it again, so you can end up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to
debug better with threading.enumerate(). I have added the line to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active, right?


finally, in terms of user experience, I think you need to give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes
















Beqa Gozalishvili
 

Hello.

I wanted to refactor this addon, but unfortunately i don't have enough
time yet, may be i will return to this in the near future, but as for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer = settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1 else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items) ==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject, wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index ==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages
gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):
gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))

On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which locks up
NVDA.
If user have bad internet connection, they can stay without speech for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on, I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"? Yes, the
scanner is WIA compliant, other apps recognize it but the addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active,
right?
I think so. I would wait for the current thread to finish before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file
not supported" (tested with PDF and BMP file types). The same file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path of the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any file
even if it is not of a supported type. In any case, supported type
or not, it always shows the result of the previous recognition,
not the requested file. Even after manually deleting the oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...


On the other hand, recognition from scanner does not work for me.
My scanner is HP Scanjet G2410, It may not be supported but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the script
is executed again, another thread is launched that also remains
active and so on forever. It is normal for the user, if he does
not receive a response, to try to run it again, so you can end up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to
debug better with threading.enumerate(). I have added the line to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active,
right?


finally, in terms of user experience, I think you need to give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes



















--
with best regards Beqa Gozalishvili
Tell: +995593454005
Email: beqaprogger@...
Web: https://gozaltech.org
Skype: beqabeqa473
Telegram: https://t.me/gozaltech
facebook: https://facebook.com/gozaltech
twitter: https://twitter.com/beqabeqa473
Instagram: https://instagram.com/beqa.gozalishvili


Rui Fontes
 

Hello Beqa!


Thanks for your suggestion!


See what I did with your suggestion:

https://www.dropbox.com/s/658ua5z2sumt1hd/tesseractOCR_2022.07.nvda-addon?dl=1


By the way, what was the purpose of:

self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)


Best regards,


Best regards,

Rui Fontes
NVDA portuguese team


Às 09:30 de 01/07/2022, Beqa Gozalishvili escreveu:

Hello.

I wanted to refactor this addon, but unfortunately i don't have enough
time yet, may be i will return to this in the near future, but as for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer = settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1 else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items) ==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject, wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index ==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages
gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):
gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))


On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which locks up
NVDA.
If user have bad internet connection, they can stay without speech for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on, I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"? Yes, the
scanner is WIA compliant, other apps recognize it but the addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active,
right?
I think so. I would wait for the current thread to finish before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file
not supported" (tested with PDF and BMP file types). The same file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path of the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any file
even if it is not of a supported type. In any case, supported type
or not, it always shows the result of the previous recognition,
not the requested file. Even after manually deleting the oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...

On the other hand, recognition from scanner does not work for me.
My scanner is HP Scanjet G2410, It may not be supported but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the script
is executed again, another thread is launched that also remains
active and so on forever. It is normal for the user, if he does
not receive a response, to try to run it again, so you can end up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to
debug better with threading.enumerate(). I have added the line to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active,
right?


finally, in terms of user experience, I think you need to give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes















Beqa Gozalishvili
 

Hello.

These methods are generated to pass direction argument to handler
function, because i didn't think of another way and i didn't want to
define two similar methods.

Yes, it is better, one more suggestion would be to remove repetition
in languages.
you have list and dictionary of languages. Lets reuse dictionary for
all possible needs and use ClientData from ItemContainer to associate
item with needed data.
Also generalise function to run things in subprocess, extract
interface class in separate module, also do the same with languages

On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Hello Beqa!


Thanks for your suggestion!


See what I did with your suggestion:

https://www.dropbox.com/s/658ua5z2sumt1hd/tesseractOCR_2022.07.nvda-addon?dl=1


By the way, what was the purpose of:

self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)


Best regards,


Best regards,

Rui Fontes
NVDA portuguese team


Às 09:30 de 01/07/2022, Beqa Gozalishvili escreveu:
Hello.

I wanted to refactor this addon, but unfortunately i don't have enough
time yet, may be i will return to this in the near future, but as for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer =
settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1 else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items) ==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject, wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index ==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages
gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):
gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))


On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which locks
up
NVDA.
If user have bad internet connection, they can stay without speech for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on, I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in
the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"? Yes, the
scanner is WIA compliant, other apps recognize it but the addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active,
right?
I think so. I would wait for the current thread to finish before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no
longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file
not supported" (tested with PDF and BMP file types). The same
file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any
file
even if it is not of a supported type. In any case, supported
type
or not, it always shows the result of the previous recognition,
not the requested file. Even after manually deleting the oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...

On the other hand, recognition from scanner does not work for me.
My scanner is HP Scanjet G2410, It may not be supported but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the script
is executed again, another thread is launched that also remains
active and so on forever. It is normal for the user, if he does
not receive a response, to try to run it again, so you can end up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to
debug better with threading.enumerate(). I have added the line to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active,
right?


finally, in terms of user experience, I think you need to give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes


















--
with best regards Beqa Gozalishvili
Tell: +995593454005
Email: beqaprogger@...
Web: https://gozaltech.org
Skype: beqabeqa473
Telegram: https://t.me/gozaltech
facebook: https://facebook.com/gozaltech
twitter: https://twitter.com/beqabeqa473
Instagram: https://instagram.com/beqa.gozalishvili


Rui Fontes
 

Thanks Beqa!


You wrote:

These methods are generated to pass direction argument to handler
function, because i didn't think of another way and i didn't want to
define two similar methods.
Ok! I already make use of them...

But you have created two similar methods, onAdd and onRemove...

It is possible to use the same method with the parameters "target" and "source"?



You wrote:

... one more suggestion would be to remove repetition in languages.
you have list and dictionary of languages. Lets reuse dictionary for
all possible needs and use ClientData from ItemContainer to associate item with needed data.

Done!



You wrote:

Also generalise function to run things in subprocess...

I don't think it is possible more...

One function to generate .png files from .pdf, other to create the list of .png files, one to make OCR and one for scanning...


Maybe joining the two first, but I don't see any advantage...



You wrote:

... extract interface class in separate module...

Create a settings.py only to create the TesseractOCR setting category?



You wrote:

... also do the same with languages
This one I did not understood...

Create a languages.py to create all languages variables to be used?


New version at:

https://www.dropbox.com/s/mwtmupoetd0le8j/tesseractOCR_2022.07.nvda-addon?dl=1


Best regards,

Rui Fontes
NVDA portuguese team


On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Hello Beqa!


Thanks for your suggestion!


See what I did with your suggestion:

https://www.dropbox.com/s/658ua5z2sumt1hd/tesseractOCR_2022.07.nvda-addon?dl=1


By the way, what was the purpose of:

self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)


Best regards,


Best regards,

Rui Fontes
NVDA portuguese team


Às 09:30 de 01/07/2022, Beqa Gozalishvili escreveu:
Hello.

I wanted to refactor this addon, but unfortunately i don't have enough
time yet, may be i will return to this in the near future, but as for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer =
settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1 else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items) ==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject, wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index ==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages
gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):
gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))


On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which locks
up
NVDA.
If user have bad internet connection, they can stay without speech for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on, I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in
the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"? Yes, the
scanner is WIA compliant, other apps recognize it but the addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active,
right?
I think so. I would wait for the current thread to finish before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no
longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things happen:

1. If this is the first time a file is recognized, it says "file
not supported" (tested with PDF and BMP file types). The same
file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any
file
even if it is not of a supported type. In any case, supported
type
or not, it always shows the result of the previous recognition,
not the requested file. Even after manually deleting the oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...

On the other hand, recognition from scanner does not work for me.
My scanner is HP Scanjet G2410, It may not be supported but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the script
is executed again, another thread is launched that also remains
active and so on forever. It is normal for the user, if he does
not receive a response, to try to run it again, so you can end up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to
debug better with threading.enumerate(). I have added the line to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active,
right?


finally, in terms of user experience, I think you need to give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes













Beqa Gozalishvili
 

Hello.

as for point 1: i ment to wrote a function which would accept command
and arguments, and don't repeat all things with other things, like
attaching startupinfo everytime and so on.
2. Yes, move all interface related code in a separate module to simplify code.
3. Also for languages, move language definitions into a separate module.

On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Thanks Beqa!


You wrote:

These methods are generated to pass direction argument to handler
function, because i didn't think of another way and i didn't want to
define two similar methods.
Ok! I already make use of them...

But you have created two similar methods, onAdd and onRemove...

It is possible to use the same method with the parameters "target" and
"source"?



You wrote:

... one more suggestion would be to remove repetition in languages.
you have list and dictionary of languages. Lets reuse dictionary for
all possible needs and use ClientData from ItemContainer to associate item
with needed data.

Done!



You wrote:

Also generalise function to run things in subprocess...

I don't think it is possible more...

One function to generate .png files from .pdf, other to create the list
of .png files, one to make OCR and one for scanning...


Maybe joining the two first, but I don't see any advantage...



You wrote:

... extract interface class in separate module...

Create a settings.py only to create the TesseractOCR setting category?



You wrote:

... also do the same with languages
This one I did not understood...

Create a languages.py to create all languages variables to be used?


New version at:

https://www.dropbox.com/s/mwtmupoetd0le8j/tesseractOCR_2022.07.nvda-addon?dl=1


Best regards,

Rui Fontes
NVDA portuguese team


On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Hello Beqa!


Thanks for your suggestion!


See what I did with your suggestion:

https://www.dropbox.com/s/658ua5z2sumt1hd/tesseractOCR_2022.07.nvda-addon?dl=1


By the way, what was the purpose of:

self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)


Best regards,


Best regards,

Rui Fontes
NVDA portuguese team


Às 09:30 de 01/07/2022, Beqa Gozalishvili escreveu:
Hello.

I wanted to refactor this addon, but unfortunately i don't have enough
time yet, may be i will return to this in the near future, but as for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer =
settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1
else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items)
==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject,
wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index ==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages

gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):

gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))


On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which
locks
up
NVDA.
If user have bad internet connection, they can stay without speech
for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have
selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on,
I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in
the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"? Yes, the
scanner is WIA compliant, other apps recognize it but the addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active,
right?
I think so. I would wait for the current thread to finish before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no
longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking
too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things
happen:

1. If this is the first time a file is recognized, it says
"file
not supported" (tested with PDF and BMP file types). The same
file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any
file
even if it is not of a supported type. In any case, supported
type
or not, it always shows the result of the previous recognition,
not the requested file. Even after manually deleting the oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...

On the other hand, recognition from scanner does not work for
me.
My scanner is HP Scanjet G2410, It may not be supported but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the
script
is executed again, another thread is launched that also remains
active and so on forever. It is normal for the user, if he does
not receive a response, to try to run it again, so you can end
up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to
debug better with threading.enumerate(). I have added the line
to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active,
right?


finally, in terms of user experience, I think you need to give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes
















--
with best regards Beqa Gozalishvili
Tell: +995593454005
Email: beqaprogger@...
Web: https://gozaltech.org
Skype: beqabeqa473
Telegram: https://t.me/gozaltech
facebook: https://facebook.com/gozaltech
twitter: https://twitter.com/beqabeqa473
Instagram: https://instagram.com/beqa.gozalishvili


Rui Fontes
 

Hello Beqa!


You wrote:

as for point 1: i ment to wrote a function which would accept command
and arguments, and don't repeat all things with other things, like
attaching startupinfo everytime and so on.

Done!


Now, let's try point 2 and 3... 😊


Best regards,

Rui Fontes
NVDA portuguese team


Locutor Antonio Cezar
 

Hello. Only the new test link for add-on was missing... Thanks.



Locutor Antonio Cezar

Em 05/07/2022 21:10, Rui Fontes escreveu:

Hello Beqa!


You wrote:

as for point 1: i ment to wrote a function which would accept command
and arguments, and don't repeat all things with other things, like
attaching startupinfo everytime and so on.


Done!


Now, let's try point 2 and 3... 😊


Best regards,

Rui Fontes
NVDA portuguese team








Rui Fontes
 

Hello!


I think I managed to do everything...


Created various modules:

configPanel.py with the construction of the settings panel to include in NVDA settings;

languages.py where are listed all languages and created the variables related with choosing languages;

vars.py where are declared almost all variables.


I would like someone to review the add-on to include it in the repository...


Download link:

https://www.dropbox.com/s/j9jesl986m2hdlv/tesseractOCR_2022.07.nvda-addon?dl=1


Best regards,

Rui Fontes
NVDA portuguese team



Às 15:42 de 05/07/2022, Beqa Gozalishvili escreveu:

Hello.

as for point 1: i ment to wrote a function which would accept command
and arguments, and don't repeat all things with other things, like
attaching startupinfo everytime and so on.
2. Yes, move all interface related code in a separate module to simplify code.
3. Also for languages, move language definitions into a separate module.

On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Thanks Beqa!


You wrote:

These methods are generated to pass direction argument to handler
function, because i didn't think of another way and i didn't want to
define two similar methods.
Ok! I already make use of them...

But you have created two similar methods, onAdd and onRemove...

It is possible to use the same method with the parameters "target" and
"source"?



You wrote:

... one more suggestion would be to remove repetition in languages.
you have list and dictionary of languages. Lets reuse dictionary for
all possible needs and use ClientData from ItemContainer to associate item
with needed data.
Done!



You wrote:

Also generalise function to run things in subprocess...
I don't think it is possible more...

One function to generate .png files from .pdf, other to create the list
of .png files, one to make OCR and one for scanning...


Maybe joining the two first, but I don't see any advantage...



You wrote:

... extract interface class in separate module...
Create a settings.py only to create the TesseractOCR setting category?



You wrote:

... also do the same with languages
This one I did not understood...

Create a languages.py to create all languages variables to be used?


New version at:

https://www.dropbox.com/s/mwtmupoetd0le8j/tesseractOCR_2022.07.nvda-addon?dl=1


Best regards,

Rui Fontes
NVDA portuguese team


On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Hello Beqa!


Thanks for your suggestion!


See what I did with your suggestion:

https://www.dropbox.com/s/658ua5z2sumt1hd/tesseractOCR_2022.07.nvda-addon?dl=1


By the way, what was the purpose of:

self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)


Best regards,


Best regards,

Rui Fontes
NVDA portuguese team


Às 09:30 de 01/07/2022, Beqa Gozalishvili escreveu:
Hello.

I wanted to refactor this addon, but unfortunately i don't have enough
time yet, may be i will return to this in the near future, but as for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer =
settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1
else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items)
==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject,
wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index ==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages

gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):

gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))


On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which
locks
up
NVDA.
If user have bad internet connection, they can stay without speech
for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have
selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on,
I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in
the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"? Yes, the
scanner is WIA compliant, other apps recognize it but the addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active,
right?
I think so. I would wait for the current thread to finish before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no
longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking
too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things
happen:

1. If this is the first time a file is recognized, it says
"file
not supported" (tested with PDF and BMP file types). The same
file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any
file
even if it is not of a supported type. In any case, supported
type
or not, it always shows the result of the previous recognition,
not the requested file. Even after manually deleting the oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...

On the other hand, recognition from scanner does not work for
me.
My scanner is HP Scanjet G2410, It may not be supported but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the
script
is executed again, another thread is launched that also remains
active and so on forever. It is normal for the user, if he does
not receive a response, to try to run it again, so you can end
up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to
debug better with threading.enumerate(). I have added the line
to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active,
right?


finally, in terms of user experience, I think you need to give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes













Rui Fontes
 

Edited message, since I have changed the keystroke for digitalize from scanner...


Hello!


I think I managed to do everything...

Created various modules:
configPanel.py with the construction of the settings panel to include in NVDA settings;
languages.py where are listed all languages and created the variables related with choosing languages;
vars.py where are declared almost all variables.


The keystroke for digitalize from scanner was changed to:

Windows+Control+w


I would like someone to review the add-on to include it in the repository...

Download link:
https://www.dropbox.com/s/j9jesl986m2hdlv/tesseractOCR_2022.07.nvda-addon?dl=1

Best regards,

Rui Fontes
NVDA portuguese team



Às 15:42 de 05/07/2022, Beqa Gozalishvili escreveu:

Hello.

as for point 1: i ment to wrote a function which would accept command
and arguments, and don't repeat all things with other things, like
attaching startupinfo everytime and so on.
2. Yes, move all interface related code in a separate module to simplify code.
3. Also for languages, move language definitions into a separate module.

On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Thanks Beqa!


You wrote:

These methods are generated to pass direction argument to handler
function, because i didn't think of another way and i didn't want to
define two similar methods.
Ok! I already make use of them...

But you have created two similar methods, onAdd and onRemove...

It is possible to use the same method with the parameters "target" and
"source"?



You wrote:

... one more suggestion would be to remove repetition in languages.
you have list and dictionary of languages. Lets reuse dictionary for
all possible needs and use ClientData from ItemContainer to associate item
with needed data.
Done!



You wrote:

Also generalise function to run things in subprocess...
I don't think it is possible more...

One function to generate .png files from .pdf, other to create the list
of .png files, one to make OCR and one for scanning...


Maybe joining the two first, but I don't see any advantage...



You wrote:

... extract interface class in separate module...
Create a settings.py only to create the TesseractOCR setting category?



You wrote:

... also do the same with languages
This one I did not understood...

Create a languages.py to create all languages variables to be used?


New version at:

https://www.dropbox.com/s/mwtmupoetd0le8j/tesseractOCR_2022.07.nvda-addon?dl=1


Best regards,

Rui Fontes
NVDA portuguese team


On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Hello Beqa!


Thanks for your suggestion!


See what I did with your suggestion:

https://www.dropbox.com/s/658ua5z2sumt1hd/tesseractOCR_2022.07.nvda-addon?dl=1


By the way, what was the purpose of:

self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)


Best regards,


Best regards,

Rui Fontes
NVDA portuguese team


Às 09:30 de 01/07/2022, Beqa Gozalishvili escreveu:
Hello.

I wanted to refactor this addon, but unfortunately i don't have enough
time yet, may be i will return to this in the near future, but as for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer =
settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1
else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items)
==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject,
wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index ==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages

gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):

gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))


On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which
locks
up
NVDA.
If user have bad internet connection, they can stay without speech
for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have
selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for "Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the add-on,
I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line, in
the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io escribió:
Hello.


* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"? Yes, the
scanner is WIA compliant, other apps recognize it but the addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active,
right?
I think so. I would wait for the current thread to finish before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no
longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking
too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things
happen:

1. If this is the first time a file is recognized, it says
"file
not supported" (tested with PDF and BMP file types). The same
file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path of
the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any
file
even if it is not of a supported type. In any case, supported
type
or not, it always shows the result of the previous recognition,
not the requested file. Even after manually deleting the oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...

On the other hand, recognition from scanner does not work for
me.
My scanner is HP Scanjet G2410, It may not be supported but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the
script
is executed again, another thread is launched that also remains
active and so on forever. It is normal for the user, if he does
not receive a response, to try to run it again, so you can end
up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able to
debug better with threading.enumerate(). I have added the line
to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active,
right?


finally, in terms of user experience, I think you need to give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes













Beqa Gozalishvili
 

Hello.

Yes, it is better organised now.

I'd suggest to use shutil to do deleting operation instead of calling
del and such things.

On 7/7/22, Rui Fontes <rui.fontes@...> wrote:
Edited message, since I have changed the keystroke for digitalize from
scanner...


Hello!


I think I managed to do everything...

Created various modules:
configPanel.py with the construction of the settings panel to include in
NVDA settings;
languages.py where are listed all languages and created the variables
related with choosing languages;
vars.py where are declared almost all variables.


The keystroke for digitalize from scanner was changed to:

Windows+Control+w


I would like someone to review the add-on to include it in the
repository...

Download link:
https://www.dropbox.com/s/j9jesl986m2hdlv/tesseractOCR_2022.07.nvda-addon?dl=1

Best regards,

Rui Fontes
NVDA portuguese team



Às 15:42 de 05/07/2022, Beqa Gozalishvili escreveu:
Hello.

as for point 1: i ment to wrote a function which would accept command
and arguments, and don't repeat all things with other things, like
attaching startupinfo everytime and so on.
2. Yes, move all interface related code in a separate module to
simplify code.
3. Also for languages, move language definitions into a separate module.

On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Thanks Beqa!


You wrote:

These methods are generated to pass direction argument to handler
function, because i didn't think of another way and i didn't want to
define two similar methods.
Ok! I already make use of them...

But you have created two similar methods, onAdd and onRemove...

It is possible to use the same method with the parameters "target" and
"source"?



You wrote:

... one more suggestion would be to remove repetition in languages.
you have list and dictionary of languages. Lets reuse dictionary for
all possible needs and use ClientData from ItemContainer to
associate item
with needed data.
Done!



You wrote:

Also generalise function to run things in subprocess...
I don't think it is possible more...

One function to generate .png files from .pdf, other to create the list
of .png files, one to make OCR and one for scanning...


Maybe joining the two first, but I don't see any advantage...



You wrote:

... extract interface class in separate module...
Create a settings.py only to create the TesseractOCR setting category?



You wrote:

... also do the same with languages
This one I did not understood...

Create a languages.py to create all languages variables to be used?


New version at:

https://www.dropbox.com/s/mwtmupoetd0le8j/tesseractOCR_2022.07.nvda-addon?dl=1


Best regards,

Rui Fontes
NVDA portuguese team


On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Hello Beqa!


Thanks for your suggestion!


See what I did with your suggestion:

https://www.dropbox.com/s/658ua5z2sumt1hd/tesseractOCR_2022.07.nvda-addon?dl=1


By the way, what was the purpose of:

self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)


Best regards,


Best regards,

Rui Fontes
NVDA portuguese team


Às 09:30 de 01/07/2022, Beqa Gozalishvili escreveu:
Hello.

I wanted to refactor this addon, but unfortunately i don't have
enough
time yet, may be i will return to this in the near future, but as for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer =
settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1
else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items)
==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject,
wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index
==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages

gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):

gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))


On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which
locks
up
NVDA.
If user have bad internet connection, they can stay without speech
for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and
instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from
the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have
selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for
"Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the
add-on,
I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line,
in
the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io
escribió:
Hello.


* It is a limitation of the routine to get the complete path
of
the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"?
Yes, the
scanner is WIA compliant, other apps recognize it but the
addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active,
right?
I think so. I would wait for the current thread to finish
before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no
longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking
too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things
happen:

1. If this is the first time a file is recognized, it says
"file
not supported" (tested with PDF and BMP file types). The same
file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path
of
the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any
file
even if it is not of a supported type. In any case, supported
type
or not, it always shows the result of the previous
recognition,
not the requested file. Even after manually deleting the
oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...

On the other hand, recognition from scanner does not work for
me.
My scanner is HP Scanjet G2410, It may not be supported
but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the
script
is executed again, another thread is launched that also
remains
active and so on forever. It is normal for the user, if he
does
not receive a response, to try to run it again, so you can
end
up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able
to
debug better with threading.enumerate(). I have added the
line
to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active,
right?


finally, in terms of user experience, I think you need to
give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is
working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes
















--
with best regards Beqa Gozalishvili
Tell: +995593454005
Email: beqaprogger@...
Web: https://gozaltech.org
Skype: beqabeqa473
Telegram: https://t.me/gozaltech
facebook: https://facebook.com/gozaltech
twitter: https://twitter.com/beqabeqa473
Instagram: https://instagram.com/beqa.gozalishvili


Rui Fontes
 

In a quick search about shutils I didn't find a way to delete files... only directories...


Someone knows how to delete a file or group of files not deleting the directory?


Best regards,

Rui Fontes
NVDA portuguese team


Às 20:52 de 07/07/2022, Beqa Gozalishvili escreveu:

Hello.

Yes, it is better organised now.

I'd suggest to use shutil to do deleting operation instead of calling
del and such things.

On 7/7/22, Rui Fontes <rui.fontes@...> wrote:
Edited message, since I have changed the keystroke for digitalize from
scanner...


Hello!


I think I managed to do everything...

Created various modules:
configPanel.py with the construction of the settings panel to include in
NVDA settings;
languages.py where are listed all languages and created the variables
related with choosing languages;
vars.py where are declared almost all variables.


The keystroke for digitalize from scanner was changed to:

Windows+Control+w


I would like someone to review the add-on to include it in the
repository...

Download link:
https://www.dropbox.com/s/j9jesl986m2hdlv/tesseractOCR_2022.07.nvda-addon?dl=1

Best regards,

Rui Fontes
NVDA portuguese team



Às 15:42 de 05/07/2022, Beqa Gozalishvili escreveu:
Hello.

as for point 1: i ment to wrote a function which would accept command
and arguments, and don't repeat all things with other things, like
attaching startupinfo everytime and so on.
2. Yes, move all interface related code in a separate module to
simplify code.
3. Also for languages, move language definitions into a separate module.

On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Thanks Beqa!


You wrote:

These methods are generated to pass direction argument to handler
function, because i didn't think of another way and i didn't want to
define two similar methods.
Ok! I already make use of them...

But you have created two similar methods, onAdd and onRemove...

It is possible to use the same method with the parameters "target" and
"source"?



You wrote:

... one more suggestion would be to remove repetition in languages.
you have list and dictionary of languages. Lets reuse dictionary for
all possible needs and use ClientData from ItemContainer to
associate item
with needed data.
Done!



You wrote:

Also generalise function to run things in subprocess...
I don't think it is possible more...

One function to generate .png files from .pdf, other to create the list
of .png files, one to make OCR and one for scanning...


Maybe joining the two first, but I don't see any advantage...



You wrote:

... extract interface class in separate module...
Create a settings.py only to create the TesseractOCR setting category?



You wrote:

... also do the same with languages
This one I did not understood...

Create a languages.py to create all languages variables to be used?


New version at:

https://www.dropbox.com/s/mwtmupoetd0le8j/tesseractOCR_2022.07.nvda-addon?dl=1


Best regards,

Rui Fontes
NVDA portuguese team


On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Hello Beqa!


Thanks for your suggestion!


See what I did with your suggestion:

https://www.dropbox.com/s/658ua5z2sumt1hd/tesseractOCR_2022.07.nvda-addon?dl=1


By the way, what was the purpose of:

self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)


Best regards,


Best regards,

Rui Fontes
NVDA portuguese team


Às 09:30 de 01/07/2022, Beqa Gozalishvili escreveu:
Hello.

I wanted to refactor this addon, but unfortunately i don't have
enough
time yet, may be i will return to this in the near future, but as for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer =
settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1
else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items)
==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject,
wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index
==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages

gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):

gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))


On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which
locks
up
NVDA.
If user have bad internet connection, they can stay without speech
for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and
instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages, add
buttton, second list which will include all added languages from
the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just saved
languages are missing, and download them in the background thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have
selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for
"Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the
add-on,
I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line,
in
the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io
escribió:
Hello.


* It is a limitation of the routine to get the complete path
of
the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"?
Yes, the
scanner is WIA compliant, other apps recognize it but the
addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is active,
right?
I think so. I would wait for the current thread to finish
before
starting another.


Note that if you assign the new thread to self._thread, the old
thread will continue to run until it finishes but you will no
longer
have a reference to it. You will only be able to access it via
threading.enumerate().


You may need a method to kill threads that are stuck or taking
too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things
happen:

1. If this is the first time a file is recognized, it says
"file
not supported" (tested with PDF and BMP file types). The same
file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path
of
the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process any
file
even if it is not of a supported type. In any case, supported
type
or not, it always shows the result of the previous
recognition,
not the requested file. Even after manually deleting the
oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was not
cleared, so list of ocr-xxx.png file was created again...

On the other hand, recognition from scanner does not work for
me.
My scanner is HP Scanjet G2410, It may not be supported
but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the
script
is executed again, another thread is launched that also
remains
active and so on forever. It is normal for the user, if he
does
not receive a response, to try to run it again, so you can
end
up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able
to
debug better with threading.enumerate(). I have added the
line
to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is active,
right?


finally, in terms of user experience, I think you need to
give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is
working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes













Beqa Gozalishvili
 

os.remove() removes a file.

os.rmdir() removes an empty directory.

shutil.rmtree() deletes a directory and all its contents.

On 7/8/22, Rui Fontes <rui.fontes@...> wrote:
In a quick search about shutils I didn't find a way to delete files...
only directories...


Someone knows how to delete a file or group of files not deleting the
directory?


Best regards,

Rui Fontes
NVDA portuguese team


Às 20:52 de 07/07/2022, Beqa Gozalishvili escreveu:
Hello.

Yes, it is better organised now.

I'd suggest to use shutil to do deleting operation instead of calling
del and such things.

On 7/7/22, Rui Fontes <rui.fontes@...> wrote:
Edited message, since I have changed the keystroke for digitalize from
scanner...


Hello!


I think I managed to do everything...

Created various modules:
configPanel.py with the construction of the settings panel to include in
NVDA settings;
languages.py where are listed all languages and created the variables
related with choosing languages;
vars.py where are declared almost all variables.


The keystroke for digitalize from scanner was changed to:

Windows+Control+w


I would like someone to review the add-on to include it in the
repository...

Download link:
https://www.dropbox.com/s/j9jesl986m2hdlv/tesseractOCR_2022.07.nvda-addon?dl=1

Best regards,

Rui Fontes
NVDA portuguese team



Às 15:42 de 05/07/2022, Beqa Gozalishvili escreveu:
Hello.

as for point 1: i ment to wrote a function which would accept command
and arguments, and don't repeat all things with other things, like
attaching startupinfo everytime and so on.
2. Yes, move all interface related code in a separate module to
simplify code.
3. Also for languages, move language definitions into a separate
module.

On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Thanks Beqa!


You wrote:

These methods are generated to pass direction argument to handler
function, because i didn't think of another way and i didn't want to
define two similar methods.
Ok! I already make use of them...

But you have created two similar methods, onAdd and onRemove...

It is possible to use the same method with the parameters "target" and
"source"?



You wrote:

... one more suggestion would be to remove repetition in languages.
you have list and dictionary of languages. Lets reuse dictionary for
all possible needs and use ClientData from ItemContainer to
associate item
with needed data.
Done!



You wrote:

Also generalise function to run things in subprocess...
I don't think it is possible more...

One function to generate .png files from .pdf, other to create the
list
of .png files, one to make OCR and one for scanning...


Maybe joining the two first, but I don't see any advantage...



You wrote:

... extract interface class in separate module...
Create a settings.py only to create the TesseractOCR setting category?



You wrote:

... also do the same with languages
This one I did not understood...

Create a languages.py to create all languages variables to be used?


New version at:

https://www.dropbox.com/s/mwtmupoetd0le8j/tesseractOCR_2022.07.nvda-addon?dl=1


Best regards,

Rui Fontes
NVDA portuguese team


On 7/5/22, Rui Fontes <rui.fontes@...> wrote:
Hello Beqa!


Thanks for your suggestion!


See what I did with your suggestion:

https://www.dropbox.com/s/658ua5z2sumt1hd/tesseractOCR_2022.07.nvda-addon?dl=1


By the way, what was the purpose of:

self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)


Best regards,


Best regards,

Rui Fontes
NVDA portuguese team


Às 09:30 de 01/07/2022, Beqa Gozalishvili escreveu:
Hello.

I wanted to refactor this addon, but unfortunately i don't have
enough
time yet, may be i will return to this in the near future, but as
for
suggestions, i wrote a minimal example how it should look.

I am sending a minimal code example here.

Maybe there is a better way of doing such thing, but this is what i
could think off today morning.


import addonHandler
import functools
import globalPluginHandler
import gui
import ui
import wx

class OCRSettingsPanel(gui.SettingsPanel):
title = "TesseractOCR"

def makeSettings(self, settingsSizer):
sHelper = gui.guiHelper.BoxSizerHelper(self, sizer =
settingsSizer)
self.onMoveUp = functools.partial(self.onMove, direction=-1)
self.onMoveDown = functools.partial(self.onMove, direction=1)
self.availableLangs = sHelper.addLabeledControl("Available
languages", wx.ListBox, choices=[], style =
wx.LB_SINGLE|wx.LB_SORT)
self.addButton = sHelper.addItem(wx.Button(self, label
="&Add", name="Add"))
self.addButton.Bind(wx.EVT_BUTTON, self.onAdd)
self.enabledLangs = sHelper.addLabeledControl("Selected
languages", wx.ListBox, choices=[], style = wx.LB_SINGLE)
self.enabledLangs.Bind(wx.EVT_LISTBOX, self.onChange)
self.enabledLangs.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
self.removeButton = sHelper.addItem(wx.Button(self, label
="&Remove", name="Remove"))
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemove)
self.moveUpButton = sHelper.addItem(wx.Button(self, label
="Move &up", name="Up"))
self.moveUpButton.Bind(wx.EVT_BUTTON, self.onMoveUp)
self.moveDownButton = sHelper.addItem(wx.Button(self, label
="Move &down", name="Down"))
self.moveDownButton.Bind(wx.EVT_BUTTON, self.onMoveDown)
self.updateControlls()

def onKeyDown(self, evt):
if evt.KeyCode == wx.WXK_UP and evt.controlDown:
self.onMoveUp(evt)
elif evt.KeyCode == wx.WXK_DOWN and evt.controlDown:
self.onMoveDown(evt)
else:
evt.Skip()

def onAdd(self, evt):
self.swapItems(evt, self.availableLangs, self.enabledLangs)

def onRemove(self, evt):
self.swapItems(evt, self.enabledLangs, self.availableLangs)

def swapItems(self, evt, source, target):
index = source.GetSelection()
if index == -1:
return
target.Append(source.GetString(index))
source.Delete(index)
if len(source.Items) > 0:
source.SetSelection(index if index < source.Count - 1
else
index - 1)
self.updateControlls(evt)

def onMove(self, evt, direction):
curIndex = self.enabledLangs.GetSelection()
if curIndex == -1:
return
if direction == -1 and curIndex == 0 or direction == 1 and
curIndex == self.enabledLangs.Count - 1:
return
newIndex = curIndex + direction
curItem = self.enabledLangs.GetString(curIndex)
newItem = self.enabledLangs.GetString(newIndex)
self.enabledLangs.SetString(curIndex, newItem)
self.enabledLangs.SetString(newIndex, curItem)
self.enabledLangs.SetSelection(newIndex)
self.updateControlls(evt)

def onChange(self, evt):
self.updateControlls()

def updateControlls(self, evt=None):
# possibly adding temporary class fields to eliminate
unnnecessary computations.
index = self.enabledLangs.GetSelection()
self.moveUpButton.Disable() if index == 0 or index == -1 else
self.moveUpButton.Enable()
self.moveDownButton.Disable() if index ==
self.enabledLangs.Count - 1 or index == -1 else
self.moveDownButton.Enable()
self.addButton.Disable() if len(self.availableLangs.Items) ==
0 else self.addButton.Enable()
self.removeButton.Disable() if len(self.enabledLangs.Items)
==
0 else self.removeButton.Enable()
if evt is not None and (isinstance(evt.EventObject,
wx.Button)
and evt.EventObject.Name in ["Up", "Down"] and (index == 0 or index
==
self.enabledLangs.Count -1) or evt.EventObject.Name == "Add" and
len(self.availableLangs.Items) == 0 or evt.EventObject.Name ==
"Remove" and len(self.enabledLangs.Items) == 0):
self.enabledLangs.SetFocus()

def onSave(self):
# Config saving here
self.onSaveCallback(self.enabledLangs.Items)

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
OCRSettingsPanel.onSaveCallback = self.checkLanguages

gui.NVDASettingsDialog.categoryClasses.append(OCRSettingsPanel)

def terminate(self):

gui.NVDASettingsDialog.categoryClasses.remove(OCRSettingsPanel)

def checkLanguages(self, langs):
ui.message("+".join(langs))


On 6/28/22, Rui Fontes <rui.fontes@...> wrote:
Hi Beqa!


Thanks by the suggestions!


Can you gave me an example of such callback?


My knowledge is still very poor!


Best regards,

Rui Fontes
NVDA portuguese team



Às 13:49 de 28/06/2022, Beqa Gozalishvili escreveu:
hello.

this addon have one very serious problem.

downloading of imported language happens in the main thread which
locks
up
NVDA.
If user have bad internet connection, they can stay without
speech
for
several minutes.
this should be corrected as soon as possible.

suggestion, tesseract can handle more than two languages, and
instead
of selecting one more language as second language, i suggest a
following ui:
one list, which will include all possible tesseract languages,
add
buttton, second list which will include all added languages from
the
first list, remove button, and move up/down buttons.
on saving list of languages would be written in config and some
callback should be called, which would check if some of just
saved
languages are missing, and download them in the background
thread.


On 6/19/22, Rui Fontes <rui.fontes@...> wrote:
Hola Javi!


Firstly, thanks by your PR!


But, I would like to better it a litle more...

The case is:

When I have set this new laptop and installed Dropbox, I have
selected
to backup the desktop folder, and so my desktop full path is:

c:\Users\userName\dropbox\ambiente de trabalho

"Ambiente de trabalho" is the portuguese expression for
"Desktop"...


So, I have tried to use shlobj.getKnownFolderId to get the
correct
desktop path for all cases...

In spite of modifying the shlobj.ini and importing it in the
add-on,
I
didn't manage to do it...


I have added in shlobj.py, on the

class FolderId(str, Enum):

the following lines:

    #Desktop folder
    # The typical path is "C:\Users\Utilizador\appData\desktop
    APP_DATA_DESKTOP = "{B2C5E279-7ADD-439F-B28C-C41FE1BBF672}"

the string between quotes was found at:

https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""


Someone can help on this?


Best regards,

Rui Fontes
NVDA portuguese team



Às 17:59 de 11/06/2022, Javi Domínguez escreveu:
Hello.


You have a pull request that fixes both issues.


https://github.com/ruifontes/tesseractOCR/pull/2


Greetings


Javi Dominguez



El 05/06/2022 a las 23:01, Javi Domínguez via groups.io
escribió:
Hello again.

I have run wia-cmd-scanner.exe directly from the command line,
in
the
Windows CMD, and it has worked perfectly.

However from the addon it doesn't work. stderr shows:

b'The system cannot find the specified path.\r\n'

I hope this helps. It's late and I can't do any more research.
Tomorrow dawns very early.

Good nitht

Javi

El 05/06/2022 a las 22:24, Javi Domínguez via groups.io
escribió:
Hello.


* It is a limitation of the routine to get the complete path
of
the
file...

If you can help bettering the routine, I will be glad!
OK. I'll take a look at that.


* The scanner is recognized as WIA compatible?
what do you mean with "is recognized as WIA compatible"?
Yes, the
scanner is WIA compliant, other apps recognize it but the
addon I
don't know, it just doesn't do anything.


* What Windows version?
Windows 10 21H2 (x64) build 19044.1706


* So, I should name each thread differently...
Yes, it would be convenient.


* And, before starting another thread, verify if it is
active,
right?
I think so. I would wait for the current thread to finish
before
starting another.


Note that if you assign the new thread to self._thread, the
old
thread will continue to run until it finishes but you will no
longer
have a reference to it. You will only be able to access it
via
threading.enumerate().


You may need a method to kill threads that are stuck or
taking
too
long.


Greetings


Javi


El 05/06/2022 a las 20:34, Rui Fontes escribió:
Hola Javi!


Comments in midle of your message marked with *...


Às 18:47 de 05/06/2022, Javi Domínguez escreveu:
When I try to recognize a file on the desktop two things
happen:

1. If this is the first time a file is recognized, it says
"file
not supported" (tested with PDF and BMP file types). The
same
file
recognized from a folder in Windows explorer works fine.
* It is a limitation of the routine to get the complete path
of
the
file...

If you can help bettering the routine, I will be glad!


2. If another file has been recognized before, it process
any
file
even if it is not of a supported type. In any case,
supported
type
or not, it always shows the result of the previous
recognition,
not the requested file. Even after manually deleting the
oc.txt
and ocr-xxx.png files from the addon's images folder, it
re-processes the previously requested file.
* It was an error on code... The path of last document was
not
cleared, so list of ocr-xxx.png file was created again...

On the other hand, recognition from scanner does not work
for
me.
My scanner is HP Scanjet G2410, It may not be supported
but the
addon does not speak any message about it. The thread that
processes this remains active and never terminates. If the
script
is executed again, another thread is launched that also
remains
active and so on forever. It is normal for the user, if he
does
not receive a response, to try to run it again, so you can
end
up
with a lot of active threads.
* The scanner is recognized as WIA compatible?

* What Windows version?


I have the habit of naming the treads that I use to be able
to
debug better with threading.enumerate(). I have added the
line
to
__init__.py
self._thread.name = "tesseractOCR"
before starting the thread to do these tests.
* So, I should name each thread differently...

* And, before starting another thread, verify if it is
active,
right?


finally, in terms of user experience, I think you need to
give
more information about what is happening. Sometimes, if the
recognition takes time, the user does not know if it is
working
correctly or not.
* It is schedulled for next version...


Thanks!


Rui Fontes
















--
with best regards Beqa Gozalishvili
Tell: +995593454005
Email: beqaprogger@...
Web: https://gozaltech.org
Skype: beqabeqa473
Telegram: https://t.me/gozaltech
facebook: https://facebook.com/gozaltech
twitter: https://twitter.com/beqabeqa473
Instagram: https://instagram.com/beqa.gozalishvili