/** This file is part of Kiten, a KDE Japanese Reference Tool... Copyright (C) 2001 Jason Katz-Brown (C) 2005 Paul Temple (C) 2006 Joseph Kerian (C) 2006 Eric Kjeldergaard This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA **/ #include "kiten.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "resultsView.h" #include "DictQuery.h" /* Separting Learn */ //#include "learn.h" #include "kitenconfig.h" #include "configuredialog.h" #include "HistoryPtrList.h" #include "entryListView.h" #include "searchStringInput.h" kiten::kiten(QWidget *parent, const char *name) : KXmlGuiWindow(parent), radselect_proc(new KProcess(this)) { radselect_proc->setProgram(KStandardDirs::findExe("kitenradselect")); setStandardToolBarMenuEnabled(true); setObjectName(QLatin1String(name)); /* Set up the config */ config = KitenConfigSkeleton::self(); config->readConfig(); /* Set up hot keys */ //KDE4 TODO /* Accel = new KGlobalAccel(this); (void) Accel->insert("Lookup English/Japanese word", i18n("Lookup English/Japanese Word"), i18n("Looks up current text on clipboard in the same way as if you used Kiten's regular search."), Qt::CTRL + Qt::ALT + Qt::Key_S, Qt::CTRL + Qt::ALT + Qt::Key_S, this, SLOT(searchClipboardContents())); Accel->readSettings(KGlobal::config()); Accel->updateConnections(); */ /* ResultView is our main widget, displaying the results of a search */ mainView = new ResultView(this, "mainView"); /* Create the export list */ // setupExportListDock(); /* TODO: have a look at this idea detachedView = new ResultView(NULL, "detachedView"); */ setCentralWidget(mainView->widget()); setupActions(); createGUI(); StatusBar = statusBar(); optionDialog = 0; /* Start the system tray icon. */ sysTrayIcon = new KSystemTrayIcon(windowIcon(), this); sysTrayIcon->show(); /* Set things as they were (as told in the config) */ autoSearchToggle->setChecked(config->autosearch()); inputManager->setDefaultsFromConfig(); updateConfiguration(); applyMainWindowSettings( KGlobal::config()->group("kitenWindow") ); /* What happens when links are clicked or things are selected in the clipboard */ connect(mainView, SIGNAL(urlClicked(const QString &)), SLOT(searchText(const QString &))); connect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, SLOT(searchClipboard())); connect(inputManager, SIGNAL(search()), this, SLOT(searchFromEdit())); connect( mainView->view()->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(setCurrentScrollValue(int)) ); /* See below for what else needs to be done */ QTimer::singleShot(10, this, SLOT(finishInit())); } /** destructor to clean up the little bits */ kiten::~kiten() { if(radselect_proc->state() != QProcess::NotRunning) radselect_proc->kill(); delete optionDialog; optionDialog = 0; } void kiten::setupActions() { /* Add the basic quit/print/prefs actions, use the gui factory for keybindings */ (void) KStandardAction::quit(this, SLOT(close()), actionCollection()); //Why the heck is KSA:print adding it's own toolbar!? // (void) KStandardAction::print(this, SLOT(print()), actionCollection()); (void) KStandardAction::preferences(this, SLOT(slotConfigure()), actionCollection()); //old style cast seems needed here, (const QObject*) KStandardAction::keyBindings((const QObject*)guiFactory(), SLOT(configureShortcuts()), actionCollection()); /* Setup the Go-to-learn-mode actions */ /* TODO: put back when Dictionary Editor is reorganised */ // (void) new KAction(i18n("&Dictionary Editor..."), "document-properties", 0, this, SLOT(createEEdit()), actionCollection(), "dict_editor"); KAction *radselect = actionCollection()->addAction("radselect"); radselect->setText(i18n("Radical Selector")); // radselect->setIcon("edit-find"); radselect->setShortcut(Qt::CTRL+Qt::Key_R); connect(radselect,SIGNAL(triggered()), this, SLOT(radicalSearch())); /* Setup the Search Actions and our custom Edit Box */ inputManager = new searchStringInput(this); KAction *searchButton = actionCollection()->addAction( "search" ); searchButton->setText( i18n("S&earch") ); // Set the search button to search connect(searchButton, SIGNAL(triggered()), this, SLOT(searchFromEdit())); searchButton->setIcon(KStandardGuiItem::find().icon()); // That's not it, that's "find as you type"... //connect(Edit, SIGNAL(completion(const QString &)), this, SLOT(searchFromEdit())); /* Setup our widgets that handle preferences */ //deinfCB = new KToggleAction(i18n("&Deinflect Verbs in Regular Search"), 0, this, SLOT(kanjiDictChange()), actionCollection(), "deinf_toggle"); autoSearchToggle = actionCollection()->add("autosearch_toggle"); autoSearchToggle->setText(i18n("&Automatically Search Clipboard Selections")); irAction = actionCollection()->add("search_in_results"); irAction->setText(i18n("Search &in Results")); connect(irAction, SIGNAL(triggered()), this, SLOT(searchInResults())); KAction *actionFocusResultView; actionFocusResultView = actionCollection()->addAction("focusresultview", this, SLOT(focusResultView())); actionFocusResultView->setShortcut(QString("Escape")); actionFocusResultView->setText(i18n("Focus result view")); (void) KStandardAction::configureToolbars(this, SLOT(configureToolBars()), actionCollection()); //TODO: this should probably be a standardaction /* globalShortcutsAction = actionCollection()->addAction("options_configure_global_keybinding"); globalShortcutsAction->setText(i18n("Configure &Global Shortcuts...")); connect(globalShortcutsAction, SIGNAL(triggered()), this, SLOT(configureGlobalKeys())); */ globalSearchAction = actionCollection()->add("search_on_the_spot"); globalSearchAction->setText(i18n("On The Spo&t Search")); KAction *temp = qobject_cast( globalSearchAction ); KShortcut shrt("Ctrl+Alt+S"); //globalSearchAction->setGlobalShortcut(shrt); //FIXME: Why does this take ~50 seconds to return!? //connect(globalSearchAction, SIGNAL(triggered()), this, SLOT(searchOnTheSpot())); backAction = KStandardAction::back(this, SLOT(back()), actionCollection()); forwardAction = KStandardAction::forward(this, SLOT(forward()), actionCollection()); backAction->setEnabled(false); forwardAction->setEnabled(false); } void kiten::setupExportListDock() { exportListDock = new QDockWidget(i18n("Export List"), this); exportListDockContents = new QWidget(exportListDock); exportListDock->setWidget(exportListDockContents); addDockWidget(Qt::RightDockWidgetArea, exportListDock); QVBoxLayout *layout = new QVBoxLayout(exportListDockContents); exportListDockContents->setLayout(layout); exportList = new EntryListView(exportListDockContents); layout->addWidget(exportList); exportList->setModel(new EntryListModel(EntryList())); } void kiten::addExportListEntry(int index) { EntryListModel *model = qobject_cast(exportList->model()); if (!model) return; EntryList list = model->entryList(); list << historyList.current()->at(index)->clone(); model->setEntryList(list); } /** This is the latter part of the initialisation. */ void kiten::finishInit() { StatusBar->showMessage(i18n("Initialising Dictionaries")); // if it's the application's first time starting, // the app group won't exist and we show demo if (config->initialSearch()) if (!kapp->sessionConfig()->hasGroup("app")) searchTextAndRaise(QString::fromUtf8("辞書")); //Note to future tinkerers... DO NOT EDIT OR TRANSLATE THAT //it's an embedded unicode search string to find "dictionary" in japanese // Edit->Completion()->clear(); // make sure the edit is focused initially StatusBar->showMessage(i18n("Welcome to Kiten")); setCaption(QString()); } void kiten::focusResultView() { mainView->view()->setFocus(); } /** This function is run on program window close. It saves the settings in the config. */ bool kiten::queryClose() { config->setAutosearch(autoSearchToggle->isChecked()); config->writeConfig(); saveMainWindowSettings( KGlobal::config()->group( "kitenWindow") ); return true; } ////////////////////////////////////////////////////////////////////////////// // SEARCHING METHODS ////////////////////////////////////////////////////////////////////////////// /** This function searches for the contents of the Edit field in the mainwindow. * Any gui choices will also be included here. */ void kiten::searchFromEdit() { kDebug() << "SEARCH FROM EDIT CALLED"; DictQuery query = inputManager->getSearchQuery(); searchAndDisplay(query); } /** This function is called when a kanji is clicked in the result view or any other time in which we want to search for something that didn't come from the input box */ void kiten::searchText(const QString &text) { searchAndDisplay(DictQuery(text)); } /** This should change the Edit text to be appropriate and then begin a search of the dictionaries' entries. */ void kiten::searchTextAndRaise(const QString &str) { /* Do the search */ searchText(str); /* get the window as we'd like it */ raise(); } /** This function will search things as they are put in the clipboard. It checks each time to see if the autoSearchToggle is set. This function searches for the contents of the clipboard filter out long selections and selections that are contained in our current search (alleviates problem where research occurs because of X's auto-add-to-clipboard-on-select feature */ void kiten::searchClipboard() { if (autoSearchToggle->isChecked()) { QString clipboard = QApplication::clipboard()->text(QClipboard::Selection).simplified(); //Only search if the clipboard selection was less than 20 characters wide if (clipboard.length() < 20) { DictQuery potentialQuery(clipboard); //Make sure that we're not looking for a substring of the current string (needed?) if(!(potentialQuery < inputManager->getSearchQuery())) searchTextAndRaise(clipboard); kDebug() << "Clipboard item is a substring (rejected)"; } else kDebug() << "Clipboard entry too long"; } } /** This method performs the search and displays the result to the screen.. */ void kiten::searchAndDisplay(const DictQuery &query) { /* keep the user informed of what we are doing */ StatusBar->showMessage(i18n("Searching...")); /* This gorgeous incantation is all that's necessary to fill a DictQuery with a query and an Entrylist with all of the results form all of the requested dictionaries */ EntryList *results = dictionaryManager.doSearch(query); /* if there are no results */ if (results->size() == 0) //TODO: check here if the user actually prefers this { //create a modifiable copy of the original query DictQuery newQuery(query); bool tryAgain = false; do { //by default we don't try again tryAgain = false; //but if the matchtype is changed we try again if (newQuery.getMatchType() == DictQuery::matchExact) { newQuery.setMatchType(DictQuery::matchBeginning); tryAgain = true; } else if (newQuery.getMatchType() == DictQuery::matchBeginning) { newQuery.setMatchType(DictQuery::matchAnywhere); tryAgain = true; } //try another search if (tryAgain) { delete results; results = dictionaryManager.doSearch(newQuery); //results means all is ok; don't try again if (results->size() > 0) tryAgain = false; } } while (tryAgain); } /* synchronize the history (and store this pointer there) */ addHistory(results); /* Add the current search to our drop down list */ inputManager->setSearchQuery(results->getQuery()); /* suppose it's about time to show the users the results. */ displayResults(results); } /** Search in the previous results, identical to searchAndDisplay except for the one call */ void kiten::searchInResults() { StatusBar->showMessage(i18n("Searching...")); DictQuery searchQuery = inputManager->getSearchQuery(); EntryList *results = dictionaryManager.doSearchInList(searchQuery,historyList.current()); addHistory(results); inputManager->setSearchQuery(searchQuery); displayResults(results); } /** given a set of Search Results items, this function does all that's needed to put the interface into an appropriate state for those searchResults */ void kiten::displayResults(EntryList *results) { QString infoStr; /* synchronize the statusbar */ if(results->count() > 0) infoStr = i18np("Found 1 result", "Found %1 results", results->count()); else infoStr = i18n("No results found"); StatusBar->showMessage(infoStr); setCaption(infoStr); /* sort the results */ /* synchronize the results window */ if(results->count() > 0) { QStringList dictSort; QStringList fieldSort = config->field_sortlist(); if(config->dictionary_enable()=="true") dictSort = config->dictionary_sortlist(); results->sort(fieldSort,dictSort); mainView->setContents(results->toHTML()); } else mainView->setContents(""+infoStr+""); mainView->setLaterScrollValue(results->scrollValue()); /* //Debuggery: to print the html results to file: QFile file("/tmp/lala"); file.open(QIODevice::WriteOnly); file.write(results->toHTML().toUtf8()); file.close(); */ } /* void kiten::searchOnTheSpot() { kDebug() << "On the spot search!\n"; } */ void kiten::radicalSearch() { radselect_proc->start(); //Radselect is a KUniqueApplication, so we don't need to worry about opening //more than one copy } ////////////////////////////////////////////////////////////////////////////// // PREFERENCES RELATED METHODS ////////////////////////////////////////////////////////////////////////////// void kiten::slotConfigure() { //If the settings dialog is open and obscured/hidden/cached, show it if (ConfigureDialog::showDialog("settings")) return; //ConfigureDialog didn't find an instance of this dialog, so lets create it : optionDialog = new ConfigureDialog(this, config); connect(optionDialog, SIGNAL(hidden()),this,SLOT(slotConfigureHide())); connect(optionDialog, SIGNAL(settingsChanged(const QString&)), this, SLOT(updateConfiguration())); optionDialog->show(); } /** This function just queues up slotConfigureDestroy() to get around the SIGSEGV if you try to delete yourself if you are in the stack */ void kiten::slotConfigureHide() { QTimer::singleShot(0, this, SLOT(slotConfigureDestroy())); } /** This function actually tears down the optionDialog */ void kiten::slotConfigureDestroy() { if (optionDialog != 0 && optionDialog->isVisible() == 0) { delete optionDialog; optionDialog = 0; } } /* TODO: reimplement something very much like this void kiten::createEEdit() { eEdit *_eEdit = new eEdit(PERSONALDictionaryLocation("data"), this); _eEdit->show(); } */ void kiten::configureToolBars() { saveMainWindowSettings(KGlobal::config()->group( "kitenWindow") ); KEditToolBar dlg(actionCollection()); connect(&dlg, SIGNAL(newToolBarConfig()), SLOT(newToolBarConfig())); dlg.exec(); } void kiten::newToolBarConfig() { createGUI("kitenui.rc"); applyMainWindowSettings(KGlobal::config()->group( "kitenWindow") ); } /** Opens the dialog for configuring the global accelerator keys. */ void kiten::configureGlobalKeys() { //KDE4 TODO: done? KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } /** This function, as the name says, updates the configuration file. It should be called in EVERY case where the configuration files change. */ void kiten::updateConfiguration() { //Load the dictionaries of each type that we can adjust in prefs foreach(const QString &it, config->dictionary_list()) loadDictConfig(it); //Load settings for each dictionary type foreach(const QString &it, dictionaryManager.listDictFileTypes()) dictionaryManager.loadDictSettings(it,config); //Update the HTML/CSS for our fonts displayHistoryItem(); inputManager->updateFontFromConfig(); /*: TODO: have a look at this as well detachedView->updateFont(); */ //Update general options for the display manager (sorting by dict, etc) dictionaryManager.loadSettings(*config->config()); } /** This function loads the dictionaries from the config file for the program to use via the dictionaryManager object */ void kiten::loadDictConfig(const QString &dictType) { KStandardDirs *dirs = KGlobal::dirs(); KConfigGroup group = config->config()->group("dicts_"+dictType.toLower()); //A list of QPair's Name->Path QList< QPair > dictionariesToLoad; //If we need to load the global if(group.readEntry("__useGlobal", true)) dictionariesToLoad.append( qMakePair(dictType, dirs->findResource("data", QString("kiten/")+dictType.toLower()))); QStringList dictNames = group.readEntry("__NAMES", QStringList()); foreach( const QString &name, dictNames ) { QString dictPath = group.readEntry(name,QString()); if(!dictPath.isEmpty() && !name.isEmpty()) dictionariesToLoad.append( qMakePair(name,dictPath) ); } QStringList loadedDictionaries = dictionaryManager.listDictionariesOfType(dictType.toLower()); typedef QPair __dictName_t; //Can't have commas in a foreach foreach( const __dictName_t &it, dictionariesToLoad) { //Remove from the loadedDictionaries list all the dicts that we are supposed to load //This will leave only those that need to be unloaded at the end if(loadedDictionaries.removeAll(it.first) == 0 ) dictionaryManager.addDictionary(it.second,it.first,dictType.toLower()); } foreach(const QString &it, loadedDictionaries) dictionaryManager.removeDictionary(it); /* #define PERSONALDictionaryLocation( __X ) KGlobal::dirs()->saveLocation(__X, \ "kiten/dictionaries/",true).append("personal") if(!isKanji) { //Don't load personal dicts as kanji dicts QString personalDict(PERSONALDictionaryLocation("data")); if (QFile::exists(personalDict) && loadedDicts.find(personalDict) == loadedDicts.end()) { dictionaryManager.addDictionary(personalDict,"Personal",dictType.lower()); } } */ } /** This function allows one to print out the currently displayed result. */ void kiten::print() { // _ResultView->toHTML(); } /****************************************************************************** HISTORY HANDLING FUNCTIONS ******************************************************************************/ /** given one Search Result, it addes it to the history list the logic in it exists to maintain the history list of a certain size. Note that this method does not display the EntryList it is given... so you can add something to the history and display it separately. */ void kiten::addHistory(EntryList *result) { historyList.addItem(result); enableHistoryButtons(); } /** This goes back one item in the history and displays */ void kiten::back(void) { historyList.prev(); displayHistoryItem(); } /** This goes forward one item in the history and displays */ void kiten::forward(void) { historyList.next(); displayHistoryItem(); } /** This method just sets the current element in the list and triggers the display. */ void kiten::goInHistory(int index) { historyList.setCurrent(index); displayHistoryItem(); } void kiten::displayHistoryItem() { if (historyList.current() == NULL) return; inputManager->setSearchQuery(historyList.current()->getQuery()); enableHistoryButtons(); displayResults( historyList.current() ); } /** This function determines whether the forward and back buttons should be enabled. It is currently done independently of what action has just occurred. */ void kiten::enableHistoryButtons() { backAction->setEnabled(historyList.index() > 0); forwardAction->setEnabled(historyList.index()+1 < historyList.count()); } void kiten::setCurrentScrollValue(int value) { if (historyList.current() == NULL) return; historyList.current()->setScrollValue(value); } #include "kiten.moc"