Russian Qt Forum

Qt => Пользовательский интерфейс (GUI) => Тема начата: QuAzI от Декабрь 21, 2011, 20:54



Название: spellchecker
Отправлено: QuAzI от Декабрь 21, 2011, 20:54
Задачка довольно баянистая, но главная заковыка: проверять орфографию нужно в чужих окнах. За сим два вопроса
1) Как отлавливать что юзверь производит ввод в чьём-то TextEdit'е и получить его содержимое (текст).
2) Как hunspell натравить на этот TextEdit, чтобы подсветка синтаксиса велать в нём, а не отдельном окне.


Название: Re: spellchecker
Отправлено: BuRn от Декабрь 21, 2011, 21:21
в экзамплах есть пример input panel называется , как считать с эдита дальше я думаю разберешься ... если что кину свой код


Название: Re: spellchecker
Отправлено: QuAzI от Декабрь 22, 2011, 00:58
В чужих окнах. Была бы это своя форма - не возникло бы и вопроса. В данном экземпле идёт работа только с собственной формой.


Название: Re: spellchecker
Отправлено: andrew.k от Декабрь 22, 2011, 01:04
наверное нужно использовать платформозависимое API.
средствами Qt не получится.


Название: Re: spellchecker
Отправлено: QuAzI от Декабрь 24, 2011, 01:10
Код:
    #if defined(Q_WS_WIN32)
        char buff[MAX_PATH];

            HWND active = GetForegroundWindow();
            DWORD tid = GetWindowThreadProcessId(active, 0);
            DWORD cid = GetCurrentThreadId();
            AttachThreadInput(cid, tid, true);
            HWND focused = GetFocus();

            //qDebug() << GetWindowText(focused, (LPTSTR)buff, sizeof(buff));

            DWORD res = SendMessage( focused,
                                WM_GETTEXT,
                                sizeof(buff),
                                LPARAM((LPTSTR)buff));

            editor->setText(QString::fromUtf16((ushort*)buff));

            AttachThreadInput(cid, tid, false);
    #endif
Если дёргать через SendMessage, то из блокнота текст выдёргивается, но не проверяется на ошибки (это отдельно надо hunspell долбить). На ошибки проверяются только вновь введённые слова. Ну да пофиг, не сабж.
По сабжу если выбрать окошко посложнее, например окно Qt, то возвращается не содержимое, а тупо заголовок окна, как буд-то выполнен GetWindowText(). Про последний кстати писали что в случае передачи хендла контрола, а не хендла окна, должен возвращать текст, но он даже для блокнота возвращает только заголовок окна.
Есть ещё трезвые мысли, как можно выдёргивать текст из любого нужного эдита?


Название: Re: spellchecker
Отправлено: LisandreL от Декабрь 24, 2011, 10:48
По сабжу если выбрать окошко посложнее, например окно Qt
Проблема не в том, что посложнее. Проблема в том, что на Qt, а Qt-шные виджеты не порождаются через WinAPI (кроме собственно окна), а рисуются.
Поэтому для WinAPI окно Qt-приложение - это пустое окно и до такого эдита через WinAPI вы никак не достучитесь.


Название: Re: spellchecker
Отправлено: QuAzI от Декабрь 24, 2011, 11:10
Мм. Ну допустим (хотя странно). Но на EditPlus оно вообще сыпется при попытке извлечь текст. Как-то уж слишком грустно получается.


Название: Re: spellchecker
Отправлено: QuAzI от Декабрь 24, 2011, 11:45
Прошёлся по софту
Блокнот, Texter2, консоль Total Commander, Dev C++ - всё отлично.
MS Office - текст "Документ Microsoft Word"
Qt - Текст заголовка окна
QIP - пусто
FlameRobin - текст "stcwindow"
Opera - Заголовок текущего таба
Firefox - Текст заголовка окна
DrWeb - вообще китайская бНОПНЯ, реальные иероглифы, причём они меняются в зависимости от текста введённого в тестовой строке (проверял на списке исключений)
А вот с EditPlus я ошибся - оно падает не при копировании текста как такового, а из-за того что текста было много. И так падает на любом большом тексте даже в блокноте или текстере (которые вроде как работают).
Грубо говоря - вообще нифига не работает.


Название: Re: spellchecker
Отправлено: QuAzI от Январь 12, 2012, 08:49
Переделал оное на DLL с хуком под клавку (прочитать ввод с клавы, удалить лишнее, вставить правильный текст). Получилось не особо лучше. Qt, офис - хук не отрабатывает.
Второй косяк, не могу отловить где и почему, но после проверки пары-тройки слов перестаёт вообще отрабатывать хук.
Код:
#include "kbdhook.h"

QString kbdbuffer;

QString spell_dic;
Hunspell *pChecker = 0;

bool ownApp;

LRESULT CALLBACK KeyboardProc(int code,WPARAM wParam,LPARAM lParam) {
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644984(v=vs.85).aspx
//  WPARAM The virtual-key code of the key that generated the keystroke message.
//

    // lParam decomposition

    //WORD nRepeat = (WORD) lParam; // bits 0..15
    UINT nScanCode = (UINT) (lParam & 0x00F0);
    //LPARAM bContext = lParam & 0x20000000;
    //LPARAM bPrev = lParam & 0x40000000;

    LPARAM bRelease = lParam & 0x80000000;
    bool isChar = false;
    bool isLetter = false;   

    if (bRelease) // KeyUp
    {
        static BYTE keyState[256];
        GetKeyboardState(keyState);
        static WCHAR inBuffer[4] = {0};

        HWND hWnd = GetForegroundWindow();
        DWORD WinThreadProcId = GetWindowThreadProcessId(hWnd, 0);
        HKL kbLayout = GetKeyboardLayout(WinThreadProcId);

        if (ToUnicodeEx(wParam,
              nScanCode,
              keyState,
              (WCHAR*)&inBuffer,
              4,
              0,
              kbLayout) >= 1)
        {
            isChar = true;
            QChar chr = QString::fromUtf16((const ushort*)&inBuffer, 1)[0];
            if (chr.isLetter())
            {
                isLetter = true;
                kbdbuffer += chr;
            }
        }
       if ((!kbdbuffer.isEmpty()) & (!isLetter))
        {
           if (pChecker == 0)
           {
             spell_dic="ru_RU";
             pChecker = new Hunspell(spell_dic.toLatin1()+".aff",spell_dic.toLatin1()+".dic");
            }

            QByteArray encodedString;
            QString spell_encoding=QString(pChecker->get_dic_encoding());
            QTextCodec *codec = QTextCodec::codecForName(spell_encoding.toLatin1());
            encodedString = codec->fromUnicode(kbdbuffer);
            bool isValid = pChecker->spell(encodedString.data());
            if (!isValid)
            {               
                        char ** wlst;
                        int ns = pChecker->suggest(&wlst,encodedString.data());                       
                        if (ns > 0)
                        {                           
                            QString newtext;
                                    {
                                    QMenu *ckMenu = new QMenu();
                                    for (int i=0; i < ns; i++)
                                    {
                                            ckMenu->addAction( codec->toUnicode(wlst[i]) );
                                            free(wlst[i]);
                                    }
                                    free(wlst);

                                    QAction *act = ckMenu->exec(QCursor::pos());
                                    if (act != 0)
                                        newtext = act->text();
                                    ckMenu->close();
                                    delete ckMenu;
                                    }

                                if (newtext.length()>0)
                                {
                                    int dRepeat = 0;
                                    dRepeat = (kbdbuffer.length());
                                    if (isChar) dRepeat = dRepeat + 1;
                                    for (int ii=0; ii<dRepeat; ii++)
                                    {
                                        keybd_event( VK_BACK,
                                                     0x45,
                                                     KEYEVENTF_EXTENDEDKEY | 0,
                                                     0 );
                                        keybd_event( VK_BACK,
                                                     0x45,
                                                     KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP,
                                                     0 );
                                    }

                                    for (int uu=0; uu<newtext.length(); uu++)
                                    {
                                        QString substr(newtext.at(uu));
                                        static WCHAR ss;
                                        substr.toWCharArray(&ss);

                                        short vk = VkKeyScanEx(ss, kbLayout);

                                        keybd_event( LOBYTE(vk),
                                                     0,
                                                     KEYEVENTF_EXTENDEDKEY | 0,
                                                     0 );
                                        keybd_event( LOBYTE(vk),
                                                     0,
                                                     KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP,
                                                     0 );
                                    }
                                }
                        }// if ns >0
                        pChecker->free_list(&wlst, ns);
            }
            kbdbuffer.clear();
        }
       if (!isLetter)
           kbdbuffer.clear();
    }

return CallNextHookEx(0,code,wParam,lParam);
}

BOOL WINAPI DllMain(  HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved  ) {

  if (fdwReason==DLL_PROCESS_ATTACH)
  {
      QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
      QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
      QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));

     kbdbuffer = "";


    ownApp = QMfcApp::pluginInstance(hinstDLL);
    QMfcApp::enterModalLoop();
  } else
  if (fdwReason==DLL_PROCESS_DETACH)
  {
      if (pChecker!=0)
      {
        delete pChecker;
        pChecker = 0;
      }
     if (ownApp)
        {
         QMfcApp::exitModalLoop();
         delete qApp;     
        }
  }

  return TRUE;
}
Третий косяк - DLL выполняет DLL_PROCESS_ATTACH при срабатывании хука в каждом новом приложении. Соответственно DLL_PROCESS_DETACH только при его закрытии, пофиг что из своей программы я его давно UnhookWindowsHook, Unload и само приложение закрыл. Не совсем понятно получилось с ресурсами, вроде как переменные между собой не пересекаются (по крайней мере счётчик подключенных/отключенных у меня считает по единице каждый раз, а не накапливается), но глюки при этом ловлю только в путь. Особенно шикарно получается что приложение ловит в WindowsHook собственный вызов keybd_event и пытается его обработать отдельно. Попытка установки флага аки bool isTransaction = true на время ввода и его проверка при перехвате хука ничего не дали.
И ещё, как сделать чтобы API-функции экспортируемые библиотекой именовались по человечески?
Код:
#if defined(KBDHOOK_LIBRARY)
#  define KBDHOOKSHARED_EXPORT Q_DECL_EXPORT
#else
#  define KBDHOOKSHARED_EXPORT Q_DECL_IMPORT
#endif

...

extern "C" {
    KBDHOOKSHARED_EXPORT BOOL WINAPI DllMain(  HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved  );
    KBDHOOKSHARED_EXPORT LRESULT CALLBACK KeyboardProc(int code,WPARAM wParam,LPARAM lParam);
}
В экспорте имею KeyboardProc@12. Если убираю CALLBACK и WINAPI, то в экспорте имена красивые, но в этот момент хук перестаёт вызываться вообще. Т.е. SetWindowsHookEx хочет именно CALLBACK-функцию.