diff --git a/QtLing/Lexicon.cpp b/QtLing/Lexicon.cpp index f9b10ad..3cb38af 100644 --- a/QtLing/Lexicon.cpp +++ b/QtLing/Lexicon.cpp @@ -17,6 +17,7 @@ #include "SuffixCollection.h" #include "WordCollection.h" #include "Word.h" +#include "evaluation.h" #include "cparse.h" void SortQStringListFromRight(QStringList& ThisStringList); @@ -63,6 +64,8 @@ CLexicon::CLexicon( CLexicon* lexicon, bool suffix_flag) m_Hypothesis_map = new QMap; m_entropy_threshold_for_stems = 1.2; m_parent_lexicon = lexicon; + m_goldstandard = NULL; + m_eval_parses = NULL; m_category_types["Words"] = CT_word; m_category_types["Suffixal stems"] = CT_stem; @@ -100,6 +103,7 @@ CLexicon::~CLexicon() delete m_PrefixSignatures; delete m_ParaSignatures; delete m_PassiveSignatures; + delete m_goldstandard; delete m_Parses; } @@ -143,10 +147,54 @@ void CLexicon::clear_lexicon(){ m_Hypotheses = new QList; +} +// for gold standard +// Return true if evaluation succeeded +// Return false if it did not +GoldStandard* CLexicon::new_goldstandard_from_xml(QString& file_name) +{ + m_goldstandard = new GoldStandard(file_name); + return m_goldstandard; +} +bool CLexicon::do_gs_evaluation() +{ + if (m_goldstandard == nullptr) { + qDebug() << 134 << "Lexicon.cpp: evaluation failed: GoldStandard not loaded"; + return false; + } + bool evaluation_succeeded = m_goldstandard->evaluate(m_Words); + if (evaluation_succeeded) { + qDebug() << 139 << "Lexicon.cpp: evaluation completed"; + return true; + } else return false; } +EvalParses* CLexicon::new_eval_parses_from_txt(QString& file_name) +{ + m_eval_parses = new EvalParses(file_name); + return m_eval_parses; +} + +void CLexicon::delete_eval_parses() +{ + delete m_eval_parses; + m_eval_parses = NULL; +} + + +bool CLexicon::do_gs_evaluation_on_eval_parses() +{ + if (m_goldstandard == NULL || m_eval_parses == NULL) { + qDebug() << 153 << "Lexicon.cpp: evaluation failed: GoldStandard or evaluation file not loaded"; + return false; + } + bool evaluation_succeeded = m_goldstandard->evaluate(m_eval_parses); + if (evaluation_succeeded) { + qDebug() << 158 << "Lexicon.cpp: evaluation on imported parses completed"; + return true; + } else return false; bool CLexicon::stem_autobiographies_contains(QString stem) { //qDebug() << 121 << stem << m_stem_autobiographies.contains(stem); @@ -225,6 +273,12 @@ void CLexicon::dump_signatures_to_debug() } +/* Crab_1: + * Used after MainWindow::read_dx1_file, which parses the dx1 file and + * stores words and their counts into CWordCollection object in Lexicon, + * and generates the SortedStringArrays. + * + */ void CLexicon::Crab_1() { step1_from_words_to_protostems(); @@ -259,6 +313,11 @@ void CLexicon::Crab_1() * This is the first of the three initial parts of finding signatures. * This makes a cut at every point in a word where the successor frequency * is greater than 1. + + * Taking the sorted string array as input, finds protoroots and stores them + * by modifying m_suffix_protostems_2 (for suffixes) + * or m_prefix_protostems_2 (for prefixes) + * It is divided into two parts; the first finds protostems, by detecting * successfor frequency greater than 1; the second breaks a word after a protostem. */ @@ -402,7 +461,9 @@ void CLexicon::step1_from_words_to_protostems() /*! * This is the second of the three initial parts of finding signatures. - * This creates stem/affix pairs, which are put in a long list of "Parses". + * This creates stem/affix pairs, which are put in a long list of "Parses": + * QList>* m_Parses + * */ void CLexicon::step2_from_protostems_to_parses() { @@ -487,9 +548,14 @@ void CLexicon::step3_from_parses_to_stem_to_sig_maps(QString name_of_calling_f { // const int MINIMUM_NUMBER_OF_STEMS = 2; QString this_stem_t, this_suffix, this_prefix, this_affix_t, this_signature_string, this_word; + stem_list * p_this_stem_list; + //affix_set * this_ptr_to_affix_set(NULL); + map_sigstring_to_suffix_set temp_stems_to_affix_set; + // Equivalent to QMap> + map_sigstring_to_stem_list temp_signatures_to_stems; + // Equivalent to QSet Stem_to_sig_map these_stem_to_sig_maps; m_intermediate_signature_to_stems_map.clear(); //replaces "this_signature_to_stems_map" - morph_set * pSet; CWord* pWord; @@ -513,6 +579,31 @@ void CLexicon::step3_from_parses_to_stem_to_sig_maps(QString name_of_calling_f m_ProgressBar->setMaximum(these_stem_to_sig_maps.count()); int count= 0; + + // following lines should be removed, no? JG + + // + + + + if (false) { + // equivalent to QMap*>::iterator + QMapIterator stem_iter(temp_stems_to_affix_set); // part 1 + while (stem_iter.hasNext()) // make a presignature for each stem. + { qApp->processEvents(); + count ++; + m_ProgressBar->setValue(count); + stem_iter.next(); + this_stem_t = stem_iter.key(); + this_signature_string = convert_set_to_qstring (stem_iter.value()); + if ( ! temp_signatures_to_stems.contains(this_signature_string)){ + stem_list * pStemSet = new stem_list; + temp_signatures_to_stems[this_signature_string] = pStemSet; + } + temp_signatures_to_stems.value(this_signature_string)->append(this_stem_t); + } + } // end of if false; + foreach(QString this_stem_t, these_stem_to_sig_maps.keys()){ count++; m_ProgressBar->setValue(count); @@ -621,7 +712,9 @@ void CLexicon::step4_create_signatures(QString name_of_calling_function) affix_list this_affix_list = this_signature_string.split("="); if (this_stem_set->size() >= MINIMUM_NUMBER_OF_STEMS) { + // put signature strings into m_Signatures if( m_SuffixesFlag) { + // CSignature* pSig; pSig = *m_Signatures << this_signature_string; } else { pSig = *m_PrefixSignatures << this_signature_string; @@ -630,6 +723,26 @@ void CLexicon::step4_create_signatures(QString name_of_calling_function) foreach (QString this_affix_t, this_affix_list){ step4a_link_signature_and_affix(pSig,this_affix_t); } + if(false) { + // for each stem in the list of stems in the map of signatures, + // use the function link_signature_and_stem() + stem_list_iterator stem_iter(*p_this_stem_list); + while (stem_iter.hasNext()){ + this_stem_t = stem_iter.next(); + link_signature_and_stem(this_stem_t, pSig, this_signature_string); + // I think that all of the work of the next loop is already done inside "link_signature_and_stem"; + if (false) { + affix_iter_2.toFront(); + while(affix_iter_2.hasNext()){ + this_affix_t = affix_iter_2.next(); + if (this_affix_t == "NULL"){ this_affix_t = "";} + if (m_SuffixesFlag){ this_word = this_stem_t + this_affix_t;} + else { this_word = this_affix_t + this_stem_t;} + pWord = m_Words->find_or_fail(this_word); + pWord->add_to_autobiography(name_of_calling_function + "=" + this_stem_t ); + } + } + } //end of if false foreach (this_stem_t, *this_stem_set){ step4b_link_signature_and_stem_and_word(this_stem_t,pSig, this_signature_string); // qDebug() << 679 << this_signature_string; @@ -675,6 +788,9 @@ void CLexicon::step4a_link_signature_and_affix(CSignature * pSig, affix_t this_a } void CLexicon::step4b_link_signature_and_stem_and_word(stem_t this_stem_t , CSignature* pSig, QString this_signature_string) { + // add a CStem object into m_suffixal_stems/m_prefixal_stems: + // - add CSignature pointer to that CStem object + // - add CStem pointer to CSignature object CStem* pStem; QString this_affix, this_word; m_SuffixesFlag ? @@ -697,7 +813,11 @@ void CLexicon::step4b_link_signature_and_stem_and_word(stem_t this_stem_t , CSig } CWord* pWord = m_Words->get_word(this_word); if (!pWord){ + + qDebug() << this_word << "Error: this_word not found among words. Line 505" << this_stem_t << this_affix << pSig->get_key() << this_signature_string; + qDebug() << this_word << "Error: this_word not found among words. Line 577" << this_stem_t << this_affix << pSig->get_key() << this_signature_string; + } else{ stem_count += pWord->get_word_count(); pWord->add_parse_triple(this_stem_t, this_affix, pSig->get_key()); @@ -709,6 +829,8 @@ void CLexicon::step4b_link_signature_and_stem_and_word(stem_t this_stem_t , CSig pStem->set_count(stem_count); } + + bool contains(QList * list2, QList * list1){ for (int i=0; i < list1->length();i++){ bool success = false; @@ -994,8 +1116,3 @@ void CLexicon::collect_parasuffixes() } m_ParaSuffixes->sort_by_count(); }; - - - - - diff --git a/QtLing/Lexicon.h b/QtLing/Lexicon.h index 2206f23..316755c 100644 --- a/QtLing/Lexicon.h +++ b/QtLing/Lexicon.h @@ -11,6 +11,8 @@ #include #include "SignatureCollection.h" #include "Typedefs.h" +#include "evaluation.h" + class MainWindow; class CWordCollection; @@ -19,7 +21,6 @@ class CSuffixCollection; class CPrefixCollection; class QProgressBar; class CHypothesis; - class CParse; @@ -166,9 +167,9 @@ class CLexicon CPrefixCollection * m_Prefixes; CSignatureCollection * m_Signatures; CSignatureCollection * m_PrefixSignatures; - CWordCollection * m_Compounds; + CWordCollection * m_Compounds; // nothing done yet //QList> * m_Parses; - QList * m_Parses; + QList * m_Parses; // // QMap m_Parse_map; QMap m_suffix_protostems; @@ -177,23 +178,33 @@ class CLexicon // m_protostems_2 is used in order to keep track of exactly which interval of words in the word list begins // with a particular proto-stem (i.e., a word-beginning). This replaces using a huge signature to store // that same information. + + //QMap m_suffix_protostems_2; + //QMap m_prefix_protostems_2; // temporary data structures for crab1 + //QMap m_suffix_protostems_2; //QMap m_prefix_protostems_2; + bool m_SuffixesFlag; CLexicon* m_parent_lexicon; + // all of the possible continuations + // affixes that are "thrown out" in Crab2 CSignatureCollection* m_ParaSignatures; /*!< the information we have about stems which we have not yet integrated into a morphological system. */ CSuffixCollection * m_ParaSuffixes; CStemCollection * m_ResidualStems; CSignatureCollection * m_ResidualPrefixSignatures; CStemCollection * m_StemsFromSubsignatures; CSignatureCollection* m_Subsignatures; + + // Finds the difference between signatures, e.g. {ed, es, er, e, ing} vs {d, s, r, NULL} QList m_SigGraphEdgeList; /*!< the sig_graph_edges in here contain only one word associated with each. */ lxa_sig_graph_edge_map m_SigGraphEdgeMap; /*!< the sig_graph_edges in here contain lists of words associated with them. */ CSignatureCollection * m_PassiveSignatures; /*!< these signatures have stems one letter off from another signature. */ CSignatureCollection * m_SequentialSignatures; /*! signatures where one affix leads to another signature. */ + // Generalizes repeating QList * m_Hypotheses; QMap * m_Hypothesis_map; // add component 1 @@ -207,12 +218,29 @@ class CLexicon double m_entropy_threshold_for_stems; + // experiment for gold standard evaluation code + GoldStandard* m_goldstandard; + EvalParses* m_eval_parses; + // end of experiment + public: CLexicon(CLexicon* parent_lexicon = NULL, bool suffix_flag = true); public: ~CLexicon(); + // experiment for gold standard evaluation code + GoldStandard* get_goldstandard() { return m_goldstandard; } + GoldStandard* new_goldstandard_from_xml(QString& file_name); + void delete_goldstandard() { delete m_goldstandard; m_goldstandard = NULL; } + bool do_gs_evaluation(); + + EvalParses* get_eval_parses() { return m_eval_parses; } + EvalParses* new_eval_parses_from_txt(QString& file_name); + void delete_eval_parses(); + bool do_gs_evaluation_on_eval_parses(); + + void dump_signatures_to_debug(); // accessors and protostems void dump_suffixes(QList*); diff --git a/QtLing/QtLing.pro b/QtLing/QtLing.pro index 13ea476..cb0eac8 100644 --- a/QtLing/QtLing.pro +++ b/QtLing/QtLing.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui +QT += core gui xml greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -40,6 +40,14 @@ SOURCES += main.cpp\ signature_graph.cpp \ string_group.cpp \ cparse.cpp \ + lexicon_json.cpp \ + evaluation.cpp \ + evaluation_goldstandard.cpp \ + evaluation_evalparses.cpp \ + evaluation_parsemap.cpp \ + mainwindow_find.cpp \ + mainwindow_menubar.cpp \ + mainwindow_actions.cpp \ stringalignment.cpp \ allosignatures.cpp @@ -65,6 +73,9 @@ HEADERS += mainwindow.h \ mainwindow.h \ string_group.h \ cparse.h \ + evaluation.h \ + mainwindow_find.h \ + mainwindow_menubar.h \ stringalignment.h \ allosignatures.h diff --git a/QtLing/QtLing.pro.user b/QtLing/QtLing.pro.user index 3d4110f..e6238d4 100644 --- a/QtLing/QtLing.pro.user +++ b/QtLing/QtLing.pro.user @@ -1,10 +1,10 @@ - + EnvironmentId - {cba14c00-39a0-47e3-8065-920a1686ac6a} + {57e2e4ec-d925-47c8-bc22-d00f7ffe6d88} ProjectExplorer.Project.ActiveTarget @@ -59,14 +59,14 @@ ProjectExplorer.Project.Target.0 - Desktop Qt 5.6.2 clang 64bit - Desktop Qt 5.6.2 clang 64bit - qt.56.clang_64_kit + Desktop Qt 5.10.1 GCC 64bit + Desktop Qt 5.10.1 GCC 64bit + qt.qt5.5101.gcc_64_kit 0 0 0 - /Users/Tapan/QtLing/build-QtLing-Desktop_Qt_5_6_2_clang_64bit-Debug + /home/hansonlu/QtLing/build-QtLing-Desktop_Qt_5_10_1_GCC_64bit-Debug true @@ -120,13 +120,13 @@ false Debug - + Debug Qt4ProjectManager.Qt4BuildConfiguration 2 true - /Users/Tapan/QtLing/build-QtLing-Desktop_Qt_5_6_2_clang_64bit-Release + /home/hansonlu/QtLing/build-QtLing-Desktop_Qt_5_10_1_GCC_64bit-Release true @@ -180,13 +180,13 @@ false Release - + Release Qt4ProjectManager.Qt4BuildConfiguration 0 true - /Users/Tapan/QtLing/build-QtLing-Desktop_Qt_5_6_2_clang_64bit-Profile + /home/hansonlu/QtLing/build-QtLing-Desktop_Qt_5_10_1_GCC_64bit-Profile true @@ -240,7 +240,7 @@ false Profile - + Profile Qt4ProjectManager.Qt4BuildConfiguration 0 true @@ -254,7 +254,7 @@ ProjectExplorer.BuildSteps.Deploy 1 - Deploy locally + Deploy Configuration ProjectExplorer.DefaultDeployConfiguration @@ -304,13 +304,13 @@ QtLing - Qt4ProjectManager.Qt4RunConfiguration:/Users/Tapan/QtLing/QtLing/QtLing.pro + Qt4ProjectManager.Qt4RunConfiguration:/home/hansonlu/QtLing/QtLing/QtLing.pro true QtLing.pro false - /Users/Tapan/QtLing/build-QtLing-Desktop_Qt_5_6_2_clang_64bit-Debug/QtLing.app/Contents/MacOS + /home/hansonlu/QtLing/build-QtLing-Desktop_Qt_5_10_1_GCC_64bit-Debug 3768 false true diff --git a/QtLing/Signature.h b/QtLing/Signature.h index 2aa2e50..12452a6 100644 --- a/QtLing/Signature.h +++ b/QtLing/Signature.h @@ -26,6 +26,7 @@ class CSignature bool m_SuffixFlag; CSignatureCollection* m_SignatureCollection; double m_stem_entropy; + int m_json_id; public: @@ -70,6 +71,9 @@ class CSignature void sort_stems(); void sort_stems_by_count(); + void set_json_id(int id) {m_json_id = id;} + int get_json_id() {return m_json_id;} + }; diff --git a/QtLing/SignatureCollection.h b/QtLing/SignatureCollection.h index d997310..58dfb6f 100644 --- a/QtLing/SignatureCollection.h +++ b/QtLing/SignatureCollection.h @@ -15,9 +15,10 @@ class CLexicon; class CSignatureCollection { protected: + //QMap map_string_to_sig m_SignatureMap; - int m_CorpusCount; - QString m_MemberName; + int m_CorpusCount; // What is this used for? + QString m_MemberName; // what is this used for? QList m_SortList; QList m_signature_list; bool m_SortValidFlag; @@ -70,6 +71,6 @@ class CSignatureCollection void sort_signatures_by_affix_count(); void sort_each_signatures_stems_alphabetically(); - + void assign_json_id(); }; #endif // CSIGNATURECOLLECTION_H diff --git a/QtLing/Word.h b/QtLing/Word.h index f2837f9..535b173 100644 --- a/QtLing/Word.h +++ b/QtLing/Word.h @@ -39,6 +39,7 @@ class CWord QMap m_Parse_triple_map; QList m_Signatures; QList m_Autobiography; + int m_json_id; public: CWord(QString Word); CWord(CWord&); @@ -60,6 +61,9 @@ class CWord void IncrementWordCount(int n = 1); void SetWordCount(int count) { m_WordCount = count;} + void set_json_id(int id) { m_json_id = id; } + int get_json_id() { return m_json_id; } + }; #endif // CWORD_H diff --git a/QtLing/WordCollection.cpp b/QtLing/WordCollection.cpp index 9c7aaba..490a6dc 100644 --- a/QtLing/WordCollection.cpp +++ b/QtLing/WordCollection.cpp @@ -124,3 +124,13 @@ void CWordCollection::sort_word_list() std::sort(m_reverse_sort_list.begin(),m_reverse_sort_list.end(), reverse_string_compare); } + +void CWordCollection::assign_json_id() +{ + QMap::iterator wm_iter; + int id = 0; + for (wm_iter = m_WordMap.begin(); wm_iter != m_WordMap.end(); wm_iter++) { + wm_iter.value()->set_json_id(id); + id++; + } +} diff --git a/QtLing/WordCollection.h b/QtLing/WordCollection.h index 93e6c27..9e5ad5a 100644 --- a/QtLing/WordCollection.h +++ b/QtLing/WordCollection.h @@ -50,6 +50,8 @@ class CWordCollection QMap* get_map() { return & m_WordMap; } void sort_word_list(); + void assign_json_id(); + QMapIterator * get_iterator(); QMap::iterator begin() { return m_WordMap.begin();} QMap::iterator end() { return m_WordMap.end();} diff --git a/QtLing/evaluation.cpp b/QtLing/evaluation.cpp new file mode 100644 index 0000000..a93a4b4 --- /dev/null +++ b/QtLing/evaluation.cpp @@ -0,0 +1,33 @@ +#include "evaluation.h" + + +EvaluationResults::EvaluationResults() +{ + clear(); +} + +/*! + * \brief Calculates precision and recall. + * + * Replaces the values of m_total_recall and m_total_precision by new values. + */ +void EvaluationResults::calculate_precision_recall() +{ + m_total_precision = (double) m_true_positive_count / (double) m_retrieved_parse_count; + m_total_recall = (double) m_true_positive_count / (double) m_gs_parse_count; +} + +/*! + * \brief Resets values to 0 (of -1, if that value may be a denominator). + */ +void EvaluationResults::clear() +{ + m_total_recall = 0; + m_total_precision = 0; + m_gs_word_count = 0; + m_retrieved_word_count = 0; + m_overlap_word_count = 0; + m_true_positive_count = 0; + m_gs_parse_count = -1; + m_retrieved_parse_count = -1; +} diff --git a/QtLing/evaluation.h b/QtLing/evaluation.h new file mode 100644 index 0000000..496d7b7 --- /dev/null +++ b/QtLing/evaluation.h @@ -0,0 +1,199 @@ +#ifndef EVALUATION_H +#define EVALUATION_H + +/* evaluation.h: + * Contains definitions for the following classes, used for implementing + * evaluation functionality for a gold standard in Linguistica. + * + * EvaluationResults evaluation.cpp + * ParseMapHandler evaluation_parsemap.cpp + * GoldStandard evaluation_goldstandard.cpp + * EvalParses evaluation_evalparses.cpp + */ + +#include "Word.h" +class MainWindow; +class Lexicon; +class CWordCollection; +class EvalParses; + + +/*! + * \brief A object that stores the numerical output results of evaluation using + * a Gold Standard. + * + * Source code for functions in evaluation.cpp. + */ +class EvaluationResults +{ + double m_total_recall; //!< Recall. + double m_total_precision; //!< Precision. + int m_gs_word_count; //!< Number of words in Gold Standard. + int m_retrieved_word_count; //!< Number of words in evaluated set of parses. + int m_overlap_word_count; //!< Number of words existing both in Gold Standard and evaluated set of data. + int m_true_positive_count; //!< Total number of parses that appear both in Gold Standard and + //!< evaluated set. + int m_gs_parse_count; //!< Total number of parses of only the overlapping words, + //!< given by Gold Standard. + int m_retrieved_parse_count;//!< Total number of parses of only the overlapping words, + //!< found in the evaluated set of data. +public: + EvaluationResults(); + // use default constructor, copy constructor, assignment, and destructor + + void set_gs_word_count(int x) {m_gs_word_count = x;} + void set_retrieved_word_count(int x) {m_retrieved_word_count = x;} + void set_overlap_word_count(int x) {m_overlap_word_count = x;} + void set_true_positive_count(int x) {m_true_positive_count = x;} + void set_gs_parse_count(int x) {m_gs_parse_count = x;} + void set_retrieved_parse_count(int x) {m_retrieved_parse_count = x;} + int get_gs_word_count() {return m_gs_word_count;} + int get_retrieved_word_count() {return m_retrieved_word_count;} + int get_overlap_word_count() {return m_overlap_word_count;} + int get_true_positive_count() {return m_true_positive_count;} + int get_gs_parse_count() {return m_gs_parse_count;} + int get_retrieved_parse_count() {return m_retrieved_parse_count;} + double get_total_recall() {return m_total_recall;} + double get_total_precision() {return m_total_precision;} + + void clear(); + void calculate_precision_recall(); +}; + +/*! + * \brief A handler class for `QMap*>`. + * + * The handler class is a "smart pointer" to the QMap structure. When a + * ParseMapHandler object is instantiated, space is automatically allocated + * for the QMap, similarly when this object is deleted, or copied, the data + * structure pointed to by the Handler is deleted or deep-copied accordingly. + * Source code for this class is in evaluation_parsemap.cpp. + */ +class ParseMapHandler +{ +public: + typedef QMap Parse_triple_map; + typedef QMap ParseMap; + +protected: + ParseMap* m_map; + + ParseMap* clone_map() const; + void destroy_map(); +public: + ParseMapHandler(); + ParseMapHandler(const ParseMapHandler& parsemap); + ParseMapHandler& operator=(const ParseMapHandler& parsemap); + ~ParseMapHandler(); + + ParseMap& operator*() { return *m_map; } + ParseMap* operator->() { return m_map; } + + ParseMap* get_map() { return m_map; } + // void get_parse_triple_map(const QString& word); + + void add_parse_triple(const QString& word, const QString& stem, const QString& affix); +}; + +/*! + * \brief A class that reads in and stores parses of words from a Gold Standard + * XML file produced by Alchemist. Contains functions to evaluate other parsing + * algorithms using Gold Standard parses. + * + * Source code for functions in evaluation_goldstandard.cpp. + */ +class GoldStandard +{ +friend class EvalParses; +public: + typedef QMap Parse_triple_map; + typedef QMap ParseMap; + +protected: + EvaluationResults m_results; //!< Data structure to store results. + ParseMapHandler m_true_positive_parses; //!< Collection of "true positive" parses. + ParseMapHandler m_retrieved_parses; //!< Collection of parses found by Linguistica. + + QString m_XML_directory; + QString m_XML_file_name; //!< File name of Gold Standard XML document. + + ParseMapHandler m_gs_parses; //!< Collection of parses given by Gold Standard. + +public: + GoldStandard(); + GoldStandard(const GoldStandard& gs); + explicit GoldStandard(QString& XML_file_name); + GoldStandard& operator=(const GoldStandard& gs); + ~GoldStandard(); + + bool read_XML(); + bool evaluate(CWordCollection* p_word_collection); + bool evaluate(CWordCollection* p_word_collection, + EvaluationResults& results, + ParseMapHandler* p_true_positive_parses = NULL, + ParseMapHandler* p_retrieved_parses = NULL); + bool evaluate(EvalParses* p_eval_parses); + bool evaluate(ParseMapHandler& eval_parses, + EvaluationResults& results, + ParseMapHandler* p_true_positive_parses = NULL, + ParseMapHandler* p_retrieved_parses = NULL); + + double get_total_recall() { return m_results.get_total_recall(); } + double get_total_precision() { return m_results.get_total_precision(); } + + EvaluationResults& get_results() { return m_results; } + + ParseMapHandler get_true_positive_parses() { return m_true_positive_parses; } + ParseMapHandler get_retrieved_parses() { return m_retrieved_parses; } + ParseMapHandler get_gs_parses() { return m_gs_parses; } + +}; + + +/*! + * \brief A class that reads in and stores parses of words from a txt file produced + * by Morfessor. Stores results after these parses are evaluated against + * a Gold Standard. + * + * Source code for functions in evaluation_evalparses.cpp. + */ +class EvalParses +{ + friend class GoldStandard; +public: + typedef QMap Parse_triple_map; + typedef QMap ParseMap; +protected: + ParseMapHandler m_parses; + EvaluationResults m_results; + + QString m_file_name; + bool m_evaluated; + + +public: + EvalParses(); + explicit EvalParses(QString& file_name); + EvalParses(const EvalParses& eval); + EvalParses& operator=(const EvalParses& eval); + ~EvalParses(); + + + ParseMapHandler get_parses() {return m_parses;} + + double get_total_precision() {return m_results.get_total_precision();} + double get_total_recall() {return m_results.get_total_recall();} + + EvaluationResults& get_results() { return m_results; } + + bool evaluate(GoldStandard* p_gs); + + bool is_evaluated() {return m_evaluated;} + + bool read_morfessor_txt_file(); + + + +}; + +#endif // EVALUATION_H diff --git a/QtLing/evaluation_evalparses.cpp b/QtLing/evaluation_evalparses.cpp new file mode 100644 index 0000000..7f1b3f3 --- /dev/null +++ b/QtLing/evaluation_evalparses.cpp @@ -0,0 +1,72 @@ +#include "evaluation.h" +#include +#include + +EvalParses::EvalParses(): m_parses() { } + +EvalParses::EvalParses(QString& file_name): + m_parses(), m_file_name(file_name) { } + +EvalParses::EvalParses(const EvalParses& eval) +{ + m_parses = eval.m_parses; + m_results = eval.m_results; + m_file_name = eval.m_file_name; + m_evaluated = eval.m_evaluated; +} + +EvalParses& EvalParses::operator=(const EvalParses& eval) +{ + if (&eval != this) { + m_parses = eval.m_parses; + m_results = eval.m_results; + m_file_name = eval.m_file_name; + m_evaluated = eval.m_evaluated; + } + return *this; +} + +EvalParses::~EvalParses() +{ } + +bool EvalParses::evaluate(GoldStandard *p_gs) +{ + return p_gs->evaluate(this); +} + +bool EvalParses::read_morfessor_txt_file() +{ + if(m_file_name.isEmpty()) + return false; + + m_parses = ParseMapHandler(); + QFile parse_file(m_file_name); + + if (!parse_file.open(QIODevice::ReadOnly)) + return false; + + QTextStream parse_input(&parse_file); + + int word_count = 0; + + while (!parse_input.atEnd()) { + QString line = parse_input.readLine(); + if (line[0] == "#") + continue; + word_count++; + line.remove(QRegularExpression("\\/[A-Z]+")); + QStringList tokens = line.split(QRegularExpression("\\s\\+\\s|1\\s")); + QString str_word = tokens.join(""); + int word_len = str_word.length(); + int cut_position = tokens[0].length(); + int list_len = tokens.length(); + for (int i = 1; i < list_len; i++) { + QString stem = str_word.left(cut_position); + QString suffix = str_word.right(word_len - cut_position); + cut_position += tokens[i].length(); + m_parses.add_parse_triple(str_word, stem, suffix); + } + } + m_results.set_retrieved_word_count(word_count); + return true; +} diff --git a/QtLing/evaluation_goldstandard.cpp b/QtLing/evaluation_goldstandard.cpp new file mode 100644 index 0000000..fa5b263 --- /dev/null +++ b/QtLing/evaluation_goldstandard.cpp @@ -0,0 +1,911 @@ +#include +#include +#include +#include +#include +#include "Lexicon.h" +#include "Word.h" +#include "WordCollection.h" +#include "mainwindow.h" +//#include "CorpusWord.h" +#include "evaluation.h" +//#include "CorpusWordCollection.h" +//#include "TemplateCollection.h" +//#include "Typedefs.h" + +/* Things modified in other source code files: + * - in QtLing.pro, added "xml" to the list after QT += + * - Modified Lexicon.h + * - Modified mainwindow.h + */ + +GoldStandard::GoldStandard() { } + +GoldStandard::GoldStandard(QString& XML_file_name): + m_XML_file_name(XML_file_name) { } + + +GoldStandard& GoldStandard::operator=(const GoldStandard & gs) +{ + if (&gs != this) { + m_results = gs.m_results; + m_gs_parses = gs.m_gs_parses; + m_retrieved_parses = gs.m_retrieved_parses; + m_true_positive_parses = gs.m_true_positive_parses; + m_XML_directory = gs.m_XML_directory; + m_XML_file_name = gs.m_XML_file_name; + } + return *this; +} + + +GoldStandard::~GoldStandard() +{ } + +/*! + * \brief Evaluates Linguistica's output using a Gold Standard; + * stores results and relevant parses in the GoldStandard class. + * \param Object containing the parses generated by Linguistica. + * \return TRUE if evaluation succeeded, FALSE if not. + * + * After running this function, relevant output results are stored in the following members: + * * `m_results` stores relevant numerical results, including precision and recall.\n + * \see EvaluationResults. + * * `m_true_positive_parses` stores parses that are "true positives", i.e.\n + * parses that satisfy the following conditions: + * 1. belongs to a word existing both in Linguistica's analysis and in the Gold Standard + * 2. this parse is found both in `p_word_collection` and in the Gold Standard. + * * `m_retrieved_parses` stores any parses that are found in `p_word_collection` and satisfy coondition 1. above. + */ +bool GoldStandard::evaluate(CWordCollection* p_word_collection) +{ + return evaluate(p_word_collection, + m_results, + &m_true_positive_parses, + &m_retrieved_parses); +} + +/*! + * \brief Evaluates parses found in a CWordCollection object + * \param Object containing collection of parses to be evaluated. + * \param Location where numerical results (including precision and recall) are stored. + * \param NULL by default, if not NULL, location where "true positive" parses are stored. + * \param NULL by default, if not NULL, location where "retrieved" parses are stored. + * \return TRUE if evaluation succeeded, FALSE if not. + * \see GoldStandard::evaluate(CWordCollection* p_word_collection) + */ +bool GoldStandard::evaluate(CWordCollection* p_word_collection, + EvaluationResults& results, + ParseMapHandler* p_true_positive_parses, + ParseMapHandler* p_retrieved_parses) +{ + if (p_word_collection->get_count() == 0) { + QString errorMsg = "Did not find analysis Lexicon given by Linguistica.\n Please load file and analyse using Ctrl+S."; + QMessageBox::information(nullptr, "Gold Standard: Error", errorMsg, QMessageBox::Ok); + return false; + } + results.clear(); + int true_positives = 0; // number of correct parses found by lxa + int total_retrieved = 0; // number of parses retrieved by lxa + int total_correct = 0; // number of parses in gold standard + int temp_overlap_word_count = 0; // number of words found in both gold standard and lxa + int temp_gs_word_count = 0; // number of words foudn in gold standard + + ParseMap::const_iterator gs_pm_iter; + Parse_triple_map::const_iterator gs_ptm_iter, lxa_ptm_iter; + const Parse_triple_map *p_lxa_ptm, *p_gs_ptm; + CWord *p_word; + + for (gs_pm_iter = m_gs_parses->constBegin(); + gs_pm_iter != m_gs_parses->constEnd(); + gs_pm_iter++) { + temp_gs_word_count++; + const QString& str_word = gs_pm_iter.key(); + p_word = p_word_collection->get_word(str_word); + + if (p_word == NULL) { + continue; + } + // only consider words that appear both in gold standard and lxa output result + // ptm stands for parse_triple_map + temp_overlap_word_count++; + p_lxa_ptm = p_word->get_parse_triple_map(); + p_gs_ptm = gs_pm_iter.value(); + + // increment correct retrieved and total correct by iterating through ptm of gs + for (gs_ptm_iter = p_gs_ptm->constBegin(); + gs_ptm_iter != p_gs_ptm->constEnd(); + gs_ptm_iter++) { + total_correct++; + const QString& stem = gs_ptm_iter.key(); + if (p_lxa_ptm->contains(stem)) { + true_positives++; + if (p_true_positive_parses != NULL) { + const QString& suffix = gs_ptm_iter.value()->p_suffix; + // store parse into collection of true positive parses + p_true_positive_parses->add_parse_triple(str_word, stem, suffix); // **** NEED TO CHANGE **** // + // qDebug() << "[TRUE POSITIVE] " << str_word << ": " << stem << "-" << suffix; + } + } + } + + // increment total_retrived + for (lxa_ptm_iter = p_lxa_ptm->constBegin(); + lxa_ptm_iter != p_lxa_ptm->constEnd(); + lxa_ptm_iter++) { + const QString& stem = lxa_ptm_iter.key(); + if (stem != str_word) {// only count parses with non-null suffixes + total_retrieved++; + if (p_retrieved_parses != NULL) { + const QString& suffix = lxa_ptm_iter.value()->p_suffix; + // store parse into collection of retrieved parses + p_retrieved_parses->add_parse_triple(str_word, stem, suffix); // **** NEED TO CHANGE **** // + // qDebug() << "[RETRIEVED] " << str_word << ": " << stem << "-" << suffix; + } + } + } + } + + if (total_correct == 0) { + QString errorMsg = "Did not find any parses in gold standard!"; + QMessageBox::information(nullptr, "Gold Standard: Error", errorMsg, QMessageBox::Ok); + return false; + } + + if (total_retrieved == 0) { + QString errorMsg = "Did not find any parses in Linguistica's analysis\n" + "that are identical to a parse given by the Gold Standard"; + QMessageBox::information(nullptr, "Gold Standard: Error", errorMsg, QMessageBox::Ok); + return false; + } + + results.set_true_positive_count(true_positives); + results.set_retrieved_parse_count(total_retrieved); + results.set_retrieved_word_count(p_word_collection->get_count()); + results.set_gs_parse_count(total_correct); + results.set_gs_word_count(temp_gs_word_count); + results.set_overlap_word_count(temp_overlap_word_count); + + results.calculate_precision_recall(); + + //qDebug() << "True positives: " << true_positives; + //qDebug() << "Total # of correct parses in GS: " << total_correct; + //qDebug() << "Total # retrieved: " << total_retrieved; + return true; +} + + +/*! + * \brief Evaluates parses produced by Morfessor using a Gold Standard; + * stores results in the given EvalParses object, which stores the parses produced by Morfessor. + * \param Object containing the parses generated by Morfessor. + * \return TRUE if evaluation succeeded, FALSE if not. + * + * After running this function, relevant numerical results, including precision and recall, + * are stored in the `m_results` member of the EvalParses object. + */ +bool GoldStandard::evaluate(EvalParses* p_eval_parses) +{ + p_eval_parses->m_evaluated = true; + return evaluate(p_eval_parses->m_parses, p_eval_parses->m_results); +} + +/*! + * \brief Evaluates parses found in a `ParseMapHandler` object + * \param Object containing collection of parses to be evaluated. + * \param Location where numerical results (including precision and recall) are stored. + * \param NULL by default, if not NULL, location where "true positive" parses are stored. + * \param NULL by default, if not NULL, location where "retrieved" parses are stored. + * \return TRUE if evaluation succeeded, FALSE if not. + */ +bool GoldStandard::evaluate(ParseMapHandler& eval_parses, + EvaluationResults& results, + ParseMapHandler* p_true_positive_parses, + ParseMapHandler* p_retrieved_parses) +{ + if (eval_parses->size() == 0) { + QString errorMsg = "Parses are not loaded"; + QMessageBox::information(nullptr, "Gold Standard: Error", errorMsg, QMessageBox::Ok); + return false; + } + int true_positives = 0; // the total number of correct parses found by lxa + int total_retrieved = 0; // the total number of parses found by lxa + int total_correct = 0; // the total number of parses in gold standard + int temp_overlap_word_count = 0; + int temp_gs_word_count = 0; + + ParseMap::const_iterator gs_pm_iter; + ParseMap::const_iterator eval_pm_iter; + Parse_triple_map::const_iterator gs_ptm_iter, eval_ptm_iter; + const Parse_triple_map *p_eval_ptm, *p_gs_ptm; + + for (gs_pm_iter = m_gs_parses->constBegin(); + gs_pm_iter != m_gs_parses->constEnd(); + gs_pm_iter++) { + temp_gs_word_count++; + const QString& str_word = gs_pm_iter.key(); + eval_pm_iter = eval_parses->find(str_word); + + if (eval_pm_iter == eval_parses->constEnd()) { + continue; + } + + // only consider words that appear both in gold standard and lxa output result + // ptm stands for parse_triple_map + temp_overlap_word_count++; + p_eval_ptm = eval_pm_iter.value(); + p_gs_ptm = gs_pm_iter.value(); + + // increment correct retrieved and total correct by iterating through ptm of gs + for (gs_ptm_iter = p_gs_ptm->constBegin(); + gs_ptm_iter != p_gs_ptm->constEnd(); + gs_ptm_iter++) { + total_correct++; + const QString& stem = gs_ptm_iter.key(); + if (p_eval_ptm->contains(stem)) { + true_positives++; + if (p_true_positive_parses != NULL) { + const QString& suffix = gs_ptm_iter.value()->p_suffix; + // store parse into collection of true positive parses + p_true_positive_parses->add_parse_triple(str_word, stem, suffix); + // qDebug() << "[TRUE POSITIVE] " << str_word << ": " << stem << "-" << suffix; + } + } + } + + // increment total_retrived + for (eval_ptm_iter = p_eval_ptm->constBegin(); + eval_ptm_iter != p_eval_ptm->constEnd(); + eval_ptm_iter++) { + const QString& stem = eval_ptm_iter.key(); + if (stem != str_word) {// only count parses with non-null suffixes + total_retrieved++; + if (p_retrieved_parses != NULL) { + const QString& suffix = eval_ptm_iter.value()->p_suffix; + // store parse into collection of retrieved parses + p_retrieved_parses->add_parse_triple(str_word, stem, suffix); + // qDebug() << "[RETRIEVED] " << str_word << ": " << stem << "-" << suffix; + } + } + } + } + + if (total_correct == 0) { + QString errorMsg = "Did not find any parses in gold standard!"; + QMessageBox::information(nullptr, "Gold Standard: Error", errorMsg, QMessageBox::Ok); + return false; + } + + if (total_retrieved == 0) { + QString errorMsg = "Did not find any parses in Linguistica's analysis\n" + "that are identical to a parse given by the Gold Standard"; + QMessageBox::information(nullptr, "Gold Standard: Error", errorMsg, QMessageBox::Ok); + return false; + } + + results.set_true_positive_count(true_positives); + results.set_retrieved_parse_count(total_retrieved); + results.set_gs_parse_count(total_correct); + results.set_gs_word_count(temp_gs_word_count); + results.set_overlap_word_count(temp_overlap_word_count); + + results.calculate_precision_recall(); + + //qDebug() << "True positives: " << true_positives; + //qDebug() << "Total # of correct parses in GS: " << total_correct; + //qDebug() << "Total # retrieved: " << total_retrieved; + return true; +} + +void delete_ptm(QMap* ptm) +{ + typedef QMap Parse_triple_map; + Parse_triple_map::iterator ptm_iter; + for (ptm_iter = ptm->begin(); ptm_iter != ptm->end(); ptm_iter++) { + delete ptm_iter.value(); + } + delete ptm; +} + +/*! + * \brief Reads in a GoldStandard XML file, and stores parses given by + * the Gold Standard in m_gs_parses. m_XML_file_name must be set before + * using this function. + * \return True if reading succeeds. False if an error occurred (such as + * invalid XML file format. + */ +bool GoldStandard::read_XML() +{ +// XML file must be first opend to use this function + + + if (m_XML_file_name.isEmpty()) + return false; + + m_gs_parses = ParseMapHandler(); + QFile goldStdFile( m_XML_file_name ); + + if (!goldStdFile.open(QIODevice::ReadOnly)) + return false; + + QDomDocument doc( "Alchemist" ) /* author_data, document_data */; + + QString errorMsg; + int errorLine, errorColumn; + if( !doc.setContent( &goldStdFile, &errorMsg, &errorLine, &errorColumn ) ) + { +//Maybe we should put this back in. +// QMessageBox::warning( this, "Gold Standard : XML Error", +// QString( errorMsg + "\nLine: %1" + "Col: %2" ).arg( errorLine ).arg( errorColumn ), QMessageBox::Ok, NULL, NULL ); + + return false; + } + QDomElement alchemist_doc, morph, piece, string, wordnode + /* gloss, element, notes, morpheme, allomorph, + lmnt, feature, name, feature_id, instance_id */; + // + QDomNode node1, node2, node3, node4; + + QString word, stem, affix; + ParseMap::iterator word_iter; + Parse_triple* new_pt; + Parse_triple_map* new_ptm; + + bool skipWord = false; + int lastEnd, start, length, pieceLength; + + alchemist_doc = doc.documentElement(); + + if( alchemist_doc.tagName() != "alchemist-doc" ) { + errorMsg = "The XML document \"" + alchemist_doc.tagName() + "\" is not an alchemist document."; + /* old version + QMessageBox::information( NULL, "Gold Standard : XML Error", errorMsg, "OK" ); + */ + QMessageBox::information(nullptr, "Gold Standard : XML Error", errorMsg, QMessageBox::Ok); + return false; + } + + // Author data (optional) + node1 = alchemist_doc.firstChild(); + if ( !node1.isNull() && node1.isElement() && node1.nodeName() == "author-data" ) { + // Skip... + node1 = node1.nextSibling(); + } + // Document data (optional) + if ( !node1.isNull() && node1.isElement() && node1.nodeName() == "document-data" ) { + // Skip... + node1 = node1.nextSibling(); + } + // Feature list first of morphology description + if ( node1.isNull() || !node1.isElement() || node1.nodeName() != "feature-list" ) { + // TODO: add to error string + } else { + // Skip... + node1 = node1.nextSibling(); + } + // Morpheme list second + if( node1.isNull() || !node1.isElement() || node1.nodeName() != "morpheme-list" ) { + // TODO: add to error string + } else { + // Skip... + node1 = node1.nextSibling(); + } + + // Word list last.. this is what we need! + if( node1.isNull() || !node1.isElement() || node1.nodeName() != "word-list" ) { + // TODO: add to error string + } else { + //qDebug() << 288 << "Reached word list"; + node2 = node1.firstChild(); + + while( !node2.isNull() && + node2.isElement() && + node2.nodeName() == "word" ) + { + wordnode = node2.toElement(); + node2 = node2.nextSibling(); + + // score attribute + if( !wordnode.hasAttribute( "score" ) ) { + // TODO: add to error string + continue; + } else { + if( wordnode.attribute( "score" ) == "Not Scored" ) continue; + else if( wordnode.attribute( "score" ) == "Certain" ); // we want to look at these words + else if( wordnode.attribute( "score" ) == "Somewhat Certain" ) continue; + else if( wordnode.attribute( "score" ) == "Uncertain" ) continue; + } + // string element + node3 = wordnode.firstChild(); + if( !node3.isElement() || node3.nodeName() != "string" ) { + // TODO: add to error message + continue; + } + string = node3.toElement(); + + // Make new gold standard word + word = string.text(); + length = word.length(); + + new_ptm = new Parse_triple_map(); + + // gloss element + node3 = string.nextSibling(); + if( node3.isElement() && node3.nodeName() == "gloss" ) { + // Skip... + node3 = node3.nextSibling(); + } + + // affix, root, and piece elements + skipWord = false; + lastEnd = 0; + while( !node3.isNull() && + node3.isElement() && + ( node3.nodeName() == "piece" || + node3.nodeName() == "affix" || + node3.nodeName() == "root" ) && + !skipWord ) { + if( node3.nodeName() == "affix" || node3.nodeName() == "root" ) { + morph = node3.toElement(); + // string element + node4 = morph.firstChild(); + if( node4.isElement() && node4.nodeName() == "string" ) { + string = node4.toElement(); + // no need to do anything with this string + node4 = string.nextSibling(); + } else { + // TODO: add to error string + } + while( !node4.isNull() && node4.isElement() && node4.nodeName() == "piece" ) { + piece = node4.toElement(); + if( !piece.hasAttribute( "start" ) || + !piece.hasAttribute( "length" ) ) { + // TODO: add to error string + skipWord = true; + delete_ptm(new_ptm); + break; + } + if ( morph.tagName() == "affix" || morph.tagName() == "root" ) { + start = piece.attribute( "start" ).toInt(); + pieceLength = piece.attribute("length").toInt(); + + // discard word if it has empty spaces in it + if (start > lastEnd) { + skipWord = true; + delete_ptm(new_ptm); + break; + } else { + if (start != 0) { + stem = word.left(start); + affix = word.right(length - start); + new_pt = new Parse_triple(stem, affix, QString()); + new_ptm->insert(stem, new_pt); + if ( start < lastEnd ) { + stem = word.left(lastEnd); + affix = word.right(length - lastEnd); + new_pt = new Parse_triple(stem, affix, QString()); + new_ptm->insert(stem, new_pt); + } + } + lastEnd = start + pieceLength; + //pStem->CutRightBeforeHere( start ); //-- from old code + } + } + node4 = node4.nextSibling(); + } + } else { + // Word has unassigned pieces, skip... + skipWord = true; + delete_ptm(new_ptm); + } + node3 = node3.nextSibling(); + } + if( skipWord ) continue; + + word_iter = m_gs_parses->find(word); + if (word_iter == m_gs_parses->end()) { + word_iter = m_gs_parses->insert(word, new_ptm); + } else { + Parse_triple_map* existing_ptm = word_iter.value(); + for (Parse_triple_map::const_iterator ptm_iter = new_ptm->constBegin(); + ptm_iter != new_ptm->constEnd(); ptm_iter++) { + existing_ptm->insert(ptm_iter.key(), ptm_iter.value()); + } + delete_ptm(new_ptm); + } + + // notes element + if( !node3.isNull() && node3.isElement() && node3.nodeName() == "notes" ) { + // Skip... + node3 = node3.nextSibling(); + } + if ( !node3.isNull() ) { + // TODO: add to error string + } + } + } + //qDebug() << 453 << "Goldstandard.cpp: gold standard parsed"; + goldStdFile.close(); + return true; +} +/* +void LinguisticaMainWindow::GetMorphPrecisionRecallByWord( StringToCStemList& goldStdWords, + StringToParse& lxaWords, + double& totalPrecision, + double& totalRecall, + double& averagePrecision, + double& averageRecall ) +{ + CParse* pGoldStdStem; + int* goldStdStemCuts; + int goldStdStemCutsPos; + + CParse* pLxaStem; + int* lxaStemCuts; + int lxaStemCutsPos; + + int totalNumLxaWordsCompared = 0; + int totalNumGSWordsCompared = 0; + + int totalNumLxaMorphemes = 0; + int totalNumGSMorphemes = 0; + int totalNumCorrectMorphemes = 0; + int totalNumFoundMorphemes = 0; // For precision, see generalization notes below in function + + averagePrecision = 0.0; + averageRecall = 0.0; + + QString strWord; + + StringToCStemList::Iterator goldStdIt; + for( goldStdIt = goldStdWords.begin(); goldStdIt != goldStdWords.end(); goldStdIt++ ) + { + strWord = goldStdIt.key(); + + // We only look through words that exist in both spaces + if( lxaWords.find( strWord ) == lxaWords.end() ) continue; //GO TO NEXT WORD + pLxaStem = lxaWords.find( strWord ).data(); // pLxaStem is a pointer to a Stem object + + lxaStemCuts = pLxaStem->GetPieces(); // list of integers that represent cut positions + + int numLxaMorphemes, + numGSMorphemes, + numCorrectMorphemes; + + lxaStemCutsPos = goldStdStemCutsPos = 0; // indeces for going through the index arrays + totalNumLxaWordsCompared++; + + // There may be duplicates in gold standard, we should consider all + // pGoldStdStem is a pointer to a Stem object + for( pGoldStdStem = goldStdIt.data().first(); pGoldStdStem; pGoldStdStem = goldStdIt.data().next() ) + { + numLxaMorphemes = 0; + numGSMorphemes = 0; + numCorrectMorphemes = 0; + + totalNumGSWordsCompared++; // duplicates are counted + + goldStdStemCuts = pGoldStdStem->GetPieces(); // list of integers that represent cut positions + + // The word strings should match now... + Q_ASSERT( pLxaStem->Display() == pGoldStdStem->Display() ); + + // Therefore we can look at the cuts to compare the morphemes + lxaStemCutsPos = goldStdStemCutsPos = 0; // indeces for going through the two lists + while( lxaStemCutsPos < pLxaStem->Size() && goldStdStemCutsPos < pGoldStdStem->Size() ) + { //Parse::Size() returns the length of m_Pieces integer vectors + if( lxaStemCuts[ lxaStemCutsPos ] == goldStdStemCuts[ goldStdStemCutsPos ] && + lxaStemCuts[ lxaStemCutsPos + 1 ] == goldStdStemCuts[ goldStdStemCutsPos + 1 ] ) + { + // Morphemes match, increment everything + numLxaMorphemes++; + numGSMorphemes++; + numCorrectMorphemes++; + + // Move both positions + lxaStemCutsPos++; + goldStdStemCutsPos++; + } + else if( lxaStemCuts[ lxaStemCutsPos ] == goldStdStemCuts[ goldStdStemCutsPos ] ) + { + if( lxaStemCuts[ lxaStemCutsPos + 1 ] < goldStdStemCuts[ goldStdStemCutsPos + 1 ] ) + { + numLxaMorphemes++; + lxaStemCutsPos++; + } + else + { + numGSMorphemes++; + goldStdStemCutsPos++; + } + } + else + { + if( lxaStemCuts[ lxaStemCutsPos ] < goldStdStemCuts[ goldStdStemCutsPos ] ) + { + numLxaMorphemes++; + lxaStemCutsPos++; + } + else + { + numGSMorphemes++; + goldStdStemCutsPos++; + } + } + } // end of while loop + + // Handle remaining morphemes in either group + while( lxaStemCutsPos < pLxaStem->Size() ) + { + numLxaMorphemes++; + lxaStemCutsPos++; + } + while( goldStdStemCutsPos < pGoldStdStem->Size() ) + { + numGSMorphemes++; + goldStdStemCutsPos++; + } + + averageRecall += ( (double) numCorrectMorphemes / (double) numGSMorphemes ); + + totalNumGSMorphemes += numGSMorphemes; + totalNumCorrectMorphemes += numCorrectMorphemes; + } // END OF FOR LOOP ANALYSING EACH STEM IN CStemList OF A WORD IN GS + + + // Precision generalization: if Lxa finds a morpheme M in a word W, it + // gets credit for it if M appears in any of the analyses spelled W. + // From John's e-mail to Colin, July 27, 2006 + + numLxaMorphemes = 0; + int numFoundMorphemes = 0; + int piece = 1; // index for iterating through integer array + while( piece <= pLxaStem->Size() ) + { + numLxaMorphemes++; + //Iterating through the list of stems for one word found in GS + for( pGoldStdStem = goldStdIt.data().first(); pGoldStdStem; pGoldStdStem = goldStdIt.data().next() ) + { //pGoldStdStem is a pointer to a Stem object + // check if each morpheme in the Lxa parse of a word is in pGoldStdStem + if( pGoldStdStem->Contains( pLxaStem->GetPiece( piece ) ) ) + { + numFoundMorphemes++; + break; + } + } + + piece++; + } + + totalNumLxaMorphemes += numLxaMorphemes; + totalNumFoundMorphemes += numFoundMorphemes; + + averagePrecision += ( (double) numFoundMorphemes / (double) numLxaMorphemes ); + } // END OF FOR LOOP; FOR LOOP ITERATES OVER EVERY WORD IN GOLD STANDARD + + averagePrecision /= (double) totalNumLxaWordsCompared; + averageRecall /= (double) totalNumGSWordsCompared; + + totalPrecision = (double) totalNumFoundMorphemes / (double) totalNumLxaMorphemes; + totalRecall = (double) totalNumCorrectMorphemes / (double) totalNumGSMorphemes; +} + + +void LinguisticaMainWindow::GetCutPrecisionRecall( StringToCStemList& goldStdWords, + StringToParse& lxaWords, + double& totalPrecision, + double& totalRecall, + double& averagePrecision, + double& averageRecall ) +{ + CParse* pGoldStdStem; + int* goldStdStemCuts; + int goldStdStemCutsPos; + + CParse* pLxaStem; + int* lxaStemCuts; + int lxaStemCutsPos; + + int totalNumLxaWordsCompared = 0; + int totalNumGSWordsCompared = 0; + + int totalNumLxaCuts = 0; + int totalNumGSCuts = 0; + int totalNumCorrectCuts = 0; + int totalNumFoundCuts = 0; // Need different number for precision (using totalNumCorrectCuts for recall) + int totalNumOnePieceWords = 0; // One piece Lxa words are undefined for precision, we need to subtract when + // totalling + + averagePrecision = 0.0; + averageRecall = 0.0; + + QString strWord; + + StringToCStemList::Iterator goldStdIt; + for( goldStdIt = goldStdWords.begin(); goldStdIt != goldStdWords.end(); goldStdIt++ ) + { + strWord = goldStdIt.key(); + + // We only look through words that exist in both spaces + if( lxaWords.find( strWord ) == lxaWords.end() ) continue; + pLxaStem = lxaWords.find( strWord ).data(); + + lxaStemCuts = pLxaStem->GetPieces(); + + int numLxaCuts = 0, + numGSCuts = 0, + numCorrectCuts = 0, + numFoundCuts = 0; + + totalNumLxaWordsCompared++; + + Q3ValueList unionOfGSCuts; + + // There may be duplicates in gold standard, we need the union of all their cuts + for( pGoldStdStem = goldStdIt.data().first(); pGoldStdStem; pGoldStdStem = goldStdIt.data().next() ) + { + totalNumGSWordsCompared++; + + goldStdStemCuts = pGoldStdStem->GetPieces(); + + // The word strings should match here. + Q_ASSERT( pLxaStem->Display() == pGoldStdStem->Display() ); + + goldStdStemCutsPos = 0; + while( goldStdStemCutsPos < pGoldStdStem->Size() ) + { + if( unionOfGSCuts.find( goldStdStemCuts[ goldStdStemCutsPos ] ) == unionOfGSCuts.end() ) + { + unionOfGSCuts.append( goldStdStemCuts[ goldStdStemCutsPos ] ); + } + goldStdStemCutsPos++; + } + } + + lxaStemCutsPos = 0; + while( lxaStemCutsPos < pLxaStem->Size() ) + { + numLxaCuts++; + + if( unionOfGSCuts.find( lxaStemCuts[ lxaStemCutsPos ] ) != unionOfGSCuts.end() ) + { + numCorrectCuts++; + numFoundCuts++; + } + lxaStemCutsPos++; + } + + numGSCuts = unionOfGSCuts.count(); + + averageRecall += ( (double) numCorrectCuts / (double) numGSCuts ); + + totalNumGSCuts += numGSCuts; + totalNumCorrectCuts += numCorrectCuts; + + if( pLxaStem->Size() < 2 ) + { + totalNumOnePieceWords++; + + Q_ASSERT( numFoundCuts == 1 && numLxaCuts == 1 ); + numFoundCuts--; + numLxaCuts--; + + if( numFoundCuts < 0 ) numFoundCuts = 0; + if( numLxaCuts < 0 ) numLxaCuts = 0; + } + + totalNumLxaCuts += numLxaCuts; + totalNumFoundCuts += numFoundCuts; + + if( numLxaCuts > 0 ) averagePrecision += ( (double) numCorrectCuts / (double) numLxaCuts ); + } + + averagePrecision /= (double) ( totalNumLxaWordsCompared - totalNumOnePieceWords ); + averageRecall /= (double) totalNumGSWordsCompared; + + totalPrecision = (double) totalNumFoundCuts / (double) totalNumLxaCuts; + totalRecall = (double) totalNumCorrectCuts / (double) totalNumGSCuts; +} + +*/ + +/* +void LinguisticaMainWindow::GetMorphPrecisionRecall( StringToCStemList& goldStdWords, + StringToParse& lxaWords, + double& totalPrecision, + double& totalRecall, + double& averagePrecision, + double& averageRecall ) +{ + QStringList unionOfGoldStdMorphs, + unionOfLxaMorphs; + + QString strWord, strPiece; + + CParse* pLxaStem, * pGoldStdStem; + + int i, + totalNumLxaWordsCompared = 0, + totalNumGSWordsCompared = 0, + totalNumLxaMorphemes = 0, + totalNumGSMorphemes = 0, + totalNumCorrectMorphemes = 0; + + StringToCStemList::Iterator goldStdIt; + for( goldStdIt = goldStdWords.begin(); goldStdIt != goldStdWords.end(); goldStdIt++ ) + { + strWord = goldStdIt.key(); + + // We only look through words that exist in both spaces + if( lxaWords.find( strWord ) == lxaWords.end() ) continue; + pLxaStem = lxaWords.find( strWord ).data(); + + totalNumLxaWordsCompared++; + + for( i = 1; i <= pLxaStem->Size(); i++ ) + { + strPiece = pLxaStem->GetPiece(i).Display(); + if( unionOfLxaMorphs.findIndex( strPiece ) == -1 ) + { + unionOfLxaMorphs.append( strPiece ); + } + } + + // There may be duplicates in gGetPieceold standard, we need the union of all their morphemes + for( pGoldStdStem = goldStdIt.data().first(); pGoldStdStem; pGoldStdStem = goldStdIt.data().next() ) + { + totalNumGSWordsCompared++; + + for( i = 1; i <= pGoldStdStem->Size(); i++ ) + { + strPiece = pGoldStdStem->GetPiece(i).Display(); + if( unionOfGoldStdMorphs.findIndex( strPiece ) == -1 ) + { + unionOfGoldStdMorphs.append( strPiece ); + } + } + } + } + + unionOfLxaMorphs.sort(); + unionOfGoldStdMorphs.sort(); + + QStringList::Iterator lxaMorphsIt = unionOfLxaMorphs.begin(), + goldStdMorphsIt = unionOfGoldStdMorphs.begin(); + + while( lxaMorphsIt != unionOfLxaMorphs.end() && + goldStdMorphsIt != unionOfGoldStdMorphs.end() ) + { + if( *goldStdMorphsIt == *lxaMorphsIt ) + { + totalNumCorrectMorphemes++; + totalNumLxaMorphemes++; + totalNumGSMorphemes++; + + ++goldStdMorphsIt; + ++lxaMorphsIt; + } + else if( *goldStdMorphsIt > *lxaMorphsIt ) + { + totalNumLxaMorphemes++; + + ++lxaMorphsIt; + } + else // *goldStdMorphsIt < *lxaMorphsIt + { + totalNumGSMorphemes++; + + ++goldStdMorphsIt; + } + } + + totalPrecision = (double) totalNumCorrectMorphemes / (double) totalNumLxaMorphemes; + totalRecall = (double) totalNumCorrectMorphemes / (double) totalNumGSMorphemes; + + averagePrecision = totalPrecision; + averageRecall = totalRecall; +} + +*/ + + diff --git a/QtLing/evaluation_parsemap.cpp b/QtLing/evaluation_parsemap.cpp new file mode 100644 index 0000000..07355b0 --- /dev/null +++ b/QtLing/evaluation_parsemap.cpp @@ -0,0 +1,82 @@ +#include "evaluation.h" + +ParseMapHandler::ParseMapHandler(): + m_map(new ParseMap()) { } + +ParseMapHandler::ParseMapHandler(const ParseMapHandler &parsemap): + m_map(parsemap.clone_map()) { } + +ParseMapHandler& ParseMapHandler::operator=(const ParseMapHandler& parsemap) +{ + if (&parsemap != this) { + destroy_map(); + if (parsemap.m_map != NULL) + m_map = parsemap.clone_map(); + else + m_map = NULL; + } + return *this; +} + +ParseMapHandler::~ParseMapHandler() +{ + destroy_map(); +} + +/*! + * \brief Makes a deep copy of all the contents stored within a ParseMap structure, + * including all Parse_triple_maps and Parse_triples + * + * \return Pointer to a newly copied ParseMap + */ +ParseMapHandler::ParseMap *ParseMapHandler::clone_map() const +{ + if (m_map == NULL) + return NULL; + ParseMap *newmap = new ParseMap(); + for (ParseMap::const_iterator gsIter = m_map->constBegin(); + gsIter != m_map->constEnd(); gsIter++) { + Parse_triple_map *newptm = new Parse_triple_map(); + Parse_triple_map currptm = *(gsIter.value()); + for (Parse_triple_map::const_iterator ptmIter = currptm.constBegin(); + ptmIter != currptm.constEnd(); ptmIter++) { + Parse_triple *newpt = new Parse_triple(ptmIter.key(), ptmIter.value()->p_suffix, QString()); + newptm->insert(ptmIter.key(), newpt); + } + newmap->insert(gsIter.key(),newptm); + } + return newmap; +} + +/*! + * \brief Deletes all the content stored in a ParseMap. + */ +void ParseMapHandler::destroy_map() +{ + if (m_map == NULL) + return; + for (ParseMap::iterator gsIter = m_map->begin(); + gsIter != m_map->end(); gsIter++) { + Parse_triple_map ptm = *(gsIter.value()); + for (Parse_triple_map::iterator ptmIter = ptm.begin(); + ptmIter != ptm.end(); ptmIter++) { + delete ptmIter.value(); + } + delete gsIter.value(); + } + delete m_map; +} + +void ParseMapHandler::add_parse_triple(const QString& word, const QString& stem, const QString& affix) +{ + ParseMap::iterator word_iter = m_map->find(word); + if (word_iter == m_map->end()) { + Parse_triple_map* this_ptm = new Parse_triple_map(); + Parse_triple* this_triple = new Parse_triple(stem, affix, QString()); + this_ptm->insert(stem, this_triple); + m_map->insert(word, this_ptm); + } else { + Parse_triple* this_triple = new Parse_triple(stem, affix, QString()); + word_iter.value()->insert(stem, this_triple); + } +} diff --git a/QtLing/generaldefinitions.h b/QtLing/generaldefinitions.h index 5137e06..62b6bb5 100644 --- a/QtLing/generaldefinitions.h +++ b/QtLing/generaldefinitions.h @@ -39,7 +39,11 @@ enum eDataType{ e_data_signatures_graph_edges, e_data_hypotheses, e_data_hollow_suffixal_signatures, - e_data_hollow_prefixal_signatures + e_data_hollow_prefixal_signatures, + // for gold standard + e_data_gs_true_positive_parses, + e_data_gs_retrieved_parses, + e_data_gs_gold_standard_parses }; enum eDocumentType // for the Collection View @@ -223,7 +227,6 @@ enum eComponentType }; - /* enum eCurrentDisplay { diff --git a/QtLing/lexicon_json.cpp b/QtLing/lexicon_json.cpp new file mode 100644 index 0000000..7e370b4 --- /dev/null +++ b/QtLing/lexicon_json.cpp @@ -0,0 +1 @@ +#include "Lexicon.h" diff --git a/QtLing/lxamodels.cpp b/QtLing/lxamodels.cpp index 30fce55..7676e14 100644 --- a/QtLing/lxamodels.cpp +++ b/QtLing/lxamodels.cpp @@ -7,6 +7,7 @@ #include "QDebug" #include "hypothesis.h" #include "lxamodels.h" +#include "evaluation.h" #include LxaStandardItemModel::LxaStandardItemModel(MainWindow* main_window): QStandardItemModel(main_window) @@ -60,9 +61,9 @@ void LxaStandardItemModel::load_category(QString , eComponentType) void LxaStandardItemModel::load_words(CWordCollection* p_words) { QStringList labels; + clear(); labels << tr("word") << "word count" << "signatures"; setHorizontalHeaderLabels(labels); - clear(); m_Description = QString (" "); QMapIterator word_iter ( * p_words->get_map() ); while (word_iter.hasNext()) @@ -431,10 +432,150 @@ void LxaStandardItemModel::load_sig_graph_edges( QMap } -}; +} + +void LxaStandardItemModel::load_parsemap_from_gs(GoldStandard* p_gs, ParseMapHandler parsemap, const QString& type) +{ + clear(); + QStringList labels; + labels << type << "parses"; + setHorizontalHeaderLabels(labels); + + typedef QStandardItem QSI; + GoldStandard::ParseMap::ConstIterator gs_iter, pm_iter; + GoldStandard::Parse_triple_map::ConstIterator ptm_iter; + ParseMapHandler p_all_word_gsm = p_gs->get_gs_parses(); + + for (gs_iter = p_all_word_gsm->constBegin(); gs_iter != p_all_word_gsm->constEnd(); gs_iter++) { + QList items; + QString this_word = gs_iter.key(); + QSI* word_item = new QSI(this_word); + items.append(word_item); + pm_iter = parsemap->find(this_word); + if (pm_iter != parsemap->constEnd()) { + GoldStandard::Parse_triple_map* ptm = pm_iter.value(); + for (ptm_iter = ptm->constBegin(); ptm_iter != ptm->constEnd(); ptm_iter++) { + Parse_triple* this_pt = ptm_iter.value(); + QString this_parse = this_pt->p_stem + "=" + this_pt->p_suffix; + items.append(new QStandardItem(this_parse)); + } + } + appendRow(items); + } +} + +void remove_item_from_tree(const QString name, QStandardItem* item) +{ + for (int row_i = 0; row_i < item->rowCount(); ) { + QString data = item->child(row_i, 0)->data(Qt::DisplayRole).toString(); + if (data == name) { + item->removeRow(row_i); + } else { + row_i++; + } + } +} + +void MainWindow::append_eval_results(EvaluationResults& results, QStandardItem* parent_item) +{ + typedef QStandardItem QSI; + QList word_items; + QSI* word_item = new QSI(QString("Gold Standard Words")); + int gs_word_count = results.get_gs_word_count(); + QSI* word_count_item = new QSI(QString::number(gs_word_count)); + word_items.append(word_item); + word_items.append(word_count_item); + + QList retrieved_word_items; + QSI* retrieved_word_item = new QSI(QString("Retrieved Words")); + int retrieved_word_count = results.get_retrieved_word_count(); + QSI* retrieved_word_count_item = new QSI(QString::number(retrieved_word_count)); + retrieved_word_items.append(retrieved_word_item); + retrieved_word_items.append(retrieved_word_count_item); + + QList precision_items; + QSI* precision_item = new QSI(QString("Precision")); + double precision = results.get_total_precision(); + QSI* precision_value_item = new QSI(QString::number(precision)); + precision_items.append(precision_item); + precision_items.append(precision_value_item); + + QList recall_items; + QSI* recall_item = new QSI(QString("Recall")); + double recall = results.get_total_recall(); + QSI* recall_value_item = new QSI(QString::number(recall)); + recall_items.append(recall_item); + recall_items.append(recall_value_item); + + QList true_positive_items; + QSI* true_positive_item = new QSI(QString("True Positive Parses")); + int true_positive_count = results.get_true_positive_count(); + QSI* true_positive_count_item = new QSI(QString::number(true_positive_count)); + true_positive_items.append(true_positive_item); + true_positive_items.append(true_positive_count_item); + + QList correct_items; + QSI* correct_item = new QSI(QString("Gold Standard Parses")); + int correct_count = results.get_gs_parse_count(); + QSI* correct_count_item = new QSI(QString::number(correct_count)); + correct_items.append(correct_item); + correct_items.append(correct_count_item); + + QList retrieved_items; + QSI* retrieved_item = new QSI(QString("Retrieved Parses")); + int retrieved_count = results.get_retrieved_parse_count(); + QSI* retrieved_count_item = new QSI(QString::number(retrieved_count)); + retrieved_items.append(retrieved_item); + retrieved_items.append(retrieved_count_item); + + parent_item->appendRow(word_items); + parent_item->appendRow(retrieved_word_items); + parent_item->appendRow(precision_items); + parent_item->appendRow(recall_items); + parent_item->appendRow(true_positive_items); + parent_item->appendRow(correct_items); + parent_item->appendRow(retrieved_items); +} + +void MainWindow::update_TreeModel_for_gs(CLexicon* lexicon) +{ + typedef QStandardItem QSI; + QSI* parent = m_treeModel->invisibleRootItem(); + int parent_row_count = parent->rowCount(); + QSI* curr_lexicon_item = parent->child(parent_row_count-1, 0); + + remove_item_from_tree("Gold Standard", curr_lexicon_item); + + QSI* gs_item = new QSI(QString("Gold Standard")); + + append_eval_results(lexicon->get_goldstandard()->get_results(), gs_item); + + curr_lexicon_item->appendRow(gs_item); +} + +void MainWindow::update_TreeModel_for_eval(CLexicon *lexicon) +{ + if (lexicon->get_eval_parses() == NULL) { + qDebug() << "MainWindow::update_TreeModel_for_eval: " + "EvalParses not loaded"; + return; + } + + typedef QStandardItem QSI; + QSI* parent = m_treeModel->invisibleRootItem(); + int parent_row_count = parent->rowCount(); + QSI* curr_lexicon_item = parent->child(parent_row_count-1, 0); + + QSI* eval_item = new QSI(QString("Morfessor Parses")); + + append_eval_results(lexicon->get_eval_parses()->get_results(), eval_item); + + curr_lexicon_item->appendRow(eval_item); +} void MainWindow::create_or_update_TreeModel(CLexicon* lexicon) { + // possible take them out QStandardItem * parent = m_treeModel->invisibleRootItem(); QStandardItem * command_item = new QStandardItem(QString("Keyboard commands")); @@ -650,9 +791,3 @@ void MainWindow::create_or_update_TreeModel(CLexicon* lexicon) lexicon_item->appendRow(hypothesis_items); // add component 8 } - - - - - - diff --git a/QtLing/lxamodels.h b/QtLing/lxamodels.h index e2f1bb9..c17a909 100644 --- a/QtLing/lxamodels.h +++ b/QtLing/lxamodels.h @@ -3,6 +3,7 @@ #include #include #include "generaldefinitions.h" +#include "evaluation.h" class CWordCollection; class CStemCollection; class CSuffixCollection; @@ -45,8 +46,11 @@ class LxaStandardItemModel : public QStandardItemModel void load_hypotheses_2(QList*); // add component 11 void load_category(QString component_name, eComponentType); + + void load_parsemap_from_gs(GoldStandard* p_gs, ParseMapHandler parsemap, const QString &type); }; + class LxaSortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT; diff --git a/QtLing/mainwindow.cpp b/QtLing/mainwindow.cpp index c595d12..0a16728 100644 --- a/QtLing/mainwindow.cpp +++ b/QtLing/mainwindow.cpp @@ -40,7 +40,9 @@ #include "graphics.h" #include "generaldefinitions.h" #include "lxamodels.h" - +#include "mainwindow_find.h" +#include "mainwindow_menubar.h" +#include "evaluation.h" #include "string_group.h" class LxaStandardItemModel; @@ -78,6 +80,7 @@ MainWindow::MainWindow() m_Models["Hypotheses 2"] = new LxaStandardItemModel("Hypotheses 2"); + m_treeModel = new QStandardItemModel(); get_lexicon()->set_status_bar(statusBar()); @@ -85,7 +88,7 @@ MainWindow::MainWindow() // views m_leftTreeView = new LeftSideTreeView(this); m_tableView_upper_left = new UpperTableView (this); - m_tableView_upper_right = new UpperTableView (this, SIG_BY_AFFIX_COUNT); + m_tableView_upper_right = new UpperTableView (this, SIG_BY_AFFIX_COUNT); m_tableView_lower = new LowerTableView (this); m_tableView_upper_left->setSortingEnabled(true); m_tableView_upper_right->setSortingEnabled(true); @@ -94,6 +97,8 @@ MainWindow::MainWindow() m_graphic_display_flag = false; // toggle with Ctrl-G m_graphics_scene->set_signature_collection(get_lexicon()->get_signatures()); + // Status bars on top of each table view + //<-------------- set up main window widget -------------------------> // set model for tree view m_leftTreeView->setModel(m_treeModel); @@ -123,17 +128,24 @@ MainWindow::MainWindow() statusBar()->addPermanentWidget(m_ProgressBar); get_lexicon()->set_progress_bar (m_ProgressBar); - createActions(); + // Set dock-widget for find functionality, but not showing it yet + m_find_dock_widget = new FindDockWidget(this); + addDockWidget(Qt::BottomDockWidgetArea, m_find_dock_widget); + m_find_dock_widget->setVisible(false); + + createActions(); // render the main menu createStatusBar(); readSettings(); - + // resize the main window resize(QDesktopWidget().availableGeometry(this).size() * 0.7); + // set sizes of children of main splitter, i.e. left tree view and tables on the right m_mainSplitter->setSizes(QList() << 1000 <<4000); setCurrentFile(QString()); setUnifiedTitleAndToolBarOnMac(true); + // clicking on certain items in the tree view displays tables on the upper left and upper right connect(m_leftTreeView, SIGNAL(clicked(const QModelIndex&)), m_tableView_upper_left, SLOT(ShowModelsUpperTableView(const QModelIndex&))); connect(m_leftTreeView, SIGNAL(clicked(const QModelIndex&)), @@ -146,16 +158,30 @@ MainWindow::MainWindow() // connect(m_tableView_upper_left,SIGNAL(clicked(const QModelIndex & )), // m_current_graphics_scene,SLOT(display_this_item(const QModelIndex & ))); - - connect(m_tableView_upper_right,SIGNAL(clicked(const QModelIndex & )), m_tableView_lower,SLOT(display_this_item(const QModelIndex & ))); connect(m_tableView_upper_left,SIGNAL(clicked(const QModelIndex & )), m_tableView_upper_right,SLOT(display_this_affixes_signatures(const QModelIndex & ))); - - + /* Explanation for these signal-slot connections: + * A signal is sent to the m_main_menu_bar object after the following + * events are completed: + * 1. lexicon_ready(): A lexicon is read into linguistica and analysed + * by pressing ctrl_S or ctrl_P (see MainWindow::do_crab()) + * 2. xml_parsed(): An Alchemist XML file is read into linguistica and + * successfully parsed (see MainWindow::gs_read_and_parse_xml()) + * 3. morfessor_parsed(): A Morfessor txt file that contains morphological + * cuts of words is read and parsed (see MainWindow::read_morfessor_txt_file()) + * Then menu bar records whether it has received these signals and will + * change the availability of certain QActions depending on whether it has + * received the signals, e.g. for the "Evaluate current lexicon using Gold + * Standard" action to be enabled, the menu bar must have receive both the + * "lexicon_ready" signal and the "xml_parsed" signal. + */ + connect(this, SIGNAL(xml_parsed()), m_main_menu_bar, SLOT(gs_ready())); + connect(this, SIGNAL(lexicon_ready()), m_main_menu_bar, SLOT(lexicon_ready())); + connect(this, SIGNAL(morfessor_parsed()), m_main_menu_bar, SLOT(eval_parse_ready())); } void MainWindow::keyPressEvent(QKeyEvent* ke) @@ -345,6 +371,7 @@ void MainWindow::do_crab() m_leftTreeView->expandAll(); m_leftTreeView->resizeColumnToContents(0); statusBar()->showMessage("All models are loaded."); + emit lexicon_ready(); } void MainWindow::do_crab2() @@ -619,95 +646,133 @@ void MainWindow::print_prefix_signatures() file.close(); } +void MainWindow::launch_find_dock() +{ + FindDialog* find_dialog = m_find_dock_widget->get_child_dialog(); + if (sender() == findLeftAct) + find_dialog->set_search_selection(QString("Upper-left table")); + else if (sender() == findRightAct) + find_dialog->set_search_selection(QString("Upper-right table")); + else + find_dialog->set_search_selection(QString("Both upper tables")); + m_find_dock_widget->setVisible(true); +} + +/*! + * \brief Opens up a dialogue to get directory for a Gold Standard xml file. + * Parses that file and stores parse results in a GoldStandard object. + */ +void MainWindow::gs_read_and_parse_xml() +{ + CLexicon* lexicon = get_lexicon(); + + QString file_name = QFileDialog::getOpenFileName(this, + "Choose a gold standard file to open", + QString(), + "XML Files (*.xml)"); + //qDebug() << 114 << "Goldstandard.cpp: xml file opened"; + if (!file_name.isEmpty()) { + GoldStandard* gs = lexicon->new_goldstandard_from_xml(file_name); + if(gs->read_XML()) { + emit xml_parsed(); + qDebug() << 633 << "mainwindow.cpp: xml_parsed signal emitted"; + } else { + lexicon->delete_goldstandard(); + qDebug() << 636 << "mainwindow.cpp: error in opening xml file"; + } + } else { + qDebug() << 639 << "mainwindow.cpp: file cannot be opened!"; + } +} + +/*! + * \brief Does evaluation on the current lexicon with a loaded and parsed + * Gold Standard. Loads resulting parses and data from the evaluation into + * models to be shown in the TableViews and TreeView. + */ +void MainWindow::gs_evaluate() +{ + CLexicon* lexicon = get_lexicon(); + bool eval_succeeded = lexicon->do_gs_evaluation(); + if (eval_succeeded) { + GoldStandard* p_gs = lexicon->get_goldstandard(); + /* + qDebug() << 616 << "Mainwindow.cpp: evaluation succeeded\n" ; + qDebug() << "Precision: " << p_gs->get_total_precision() + << "Recall: " << p_gs->get_total_recall(); + */ + // create new model + //m_Models["Gold Standard Words"] = new LxaStandardItemModel("Gold Standard Words"); + m_Models["True Positive Parses"] = new LxaStandardItemModel("True Positive Parses"); + m_Models["Gold Standard Parses"] = new LxaStandardItemModel("Gold Standard Parses"); + m_Models["Retrieved Parses"] = new LxaStandardItemModel("Retrieved Parses"); + //m_Models["Gold Standard Words"]->load_GSMap(p_gs->get_gold_standard_parses(), "Gold Standard Words"); + m_Models["True Positive Parses"]->load_parsemap_from_gs(p_gs, p_gs->get_true_positive_parses(), "True Positives"); + m_Models["Gold Standard Parses"]->load_parsemap_from_gs(p_gs, p_gs->get_gs_parses(), "Gold Standard"); + m_Models["Retrieved Parses"]->load_parsemap_from_gs(p_gs, p_gs->get_retrieved_parses(), "Retrieved"); + + update_TreeModel_for_gs(lexicon); + + QCoreApplication::processEvents(); + + } else { + qDebug() << 663 << "Mainwindow.cpp: evaluation failed"; + } +} + +/*! + * \brief Opens up a dialogue to get directory for a Morfessor output txt file. + * Parses that txt file and stores parse results in an EvalParses object. + */ +void MainWindow::read_morfessor_txt_file() +{ + CLexicon* lexicon = get_lexicon(); + + QString file_name = QFileDialog::getOpenFileName(this, + "Choose a Morfessor output file to open", + QString(), + "Morfessor Output Files (*.txt)"); + //qDebug() << 114 << "Goldstandard.cpp: xml file opened"; + if (!file_name.isEmpty()) { + EvalParses* eval = lexicon->new_eval_parses_from_txt(file_name); + if (eval->read_morfessor_txt_file()) { + emit morfessor_parsed(); + qDebug() << 685 << "mainwindow.cpp: successfully read in morfessor txt file"; + } else { + lexicon->delete_eval_parses(); + qDebug() << 688 << "mainwindow.cpp: error in reading morfessor txt file"; + } + } else { + qDebug() << 691 << "mainwindow.cpp: file cannot be opened!"; + } +} + +/*! + * \brief Evaluates the morphological parses by Morfessor with the currently + * loaded Gold Standard. Loads resulting parses and data from the evaluation into + * models to be shown in the TreeView. + */ +void MainWindow::gs_evaluate_morfessor() +{ + CLexicon* lexicon = get_lexicon(); + bool eval_succeded = lexicon->do_gs_evaluation_on_eval_parses(); + if (eval_succeded) { + EvalParses* p_eval = lexicon->get_eval_parses(); + qDebug() << 701 << "Mainwindow.cpp: evaluation of Morfessor txt file succeeded" ; + qDebug() << "Precision: " << p_eval->get_total_precision() + << "Recall: " << p_eval->get_total_recall(); + update_TreeModel_for_eval(lexicon); + QCoreApplication::processEvents(); + } + +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Infra- and super-structure // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void MainWindow::createActions() -{ - - QMenu *fileMenu = menuBar()->addMenu(tr("&File")); - QToolBar *fileToolBar = addToolBar(tr("File")); -//---------------------> --------------------------> --------------------------> -// QAction * suffix_signature_display_action = new QAction(); -// CLexicon* lexicon = get_lexicon(); -// connect(suffix_signature_display_action, &QAction::triggered, this, &MainWindow::display_suffix_signatures ); - -// QAction * prefix_signature_display_action = new QAction(); -// connect(prefix_signature_display_action, &QAction::triggered, this, &MainWindow::display_prefix_signatures ); -//---------------------> --------------------------> --------------------------> - - - // Give a data file name, store the name, and read the file. - const QIcon openIcon = QIcon::fromTheme("document-open", QIcon("../../../../QtLing/images/open.png")); - QAction *openAct = new QAction(openIcon, tr("&Open..."), this); - openAct->setShortcuts(QKeySequence::Open); - openAct->setStatusTip(tr("Open an existing file")); - connect(openAct, &QAction::triggered, this, &MainWindow::ask_for_filename); - fileMenu->addAction(openAct); - fileToolBar->addAction(openAct); - - // No action associated with this yet. - const QIcon saveAsIcon = QIcon::fromTheme("document-save-as"); - QAction *saveAsAct = fileMenu->addAction(saveAsIcon, tr("Save &As..."), this, &MainWindow::saveAs); - saveAsAct->setShortcuts(QKeySequence::SaveAs); - saveAsAct->setStatusTip(tr("Save the document under a new name")); - - fileMenu->addSeparator(); - - const QIcon exitIcon = QIcon::fromTheme("application-exit"); - QAction *exitAct = fileMenu->addAction(exitIcon, tr("E&xit"), this, &QWidget::close); - exitAct->setShortcuts(QKeySequence::Quit); - exitAct->setStatusTip(tr("Exit the application")); - - QMenu *editMenu = menuBar()->addMenu(tr("&Edit")); - QToolBar *editToolBar = addToolBar(tr("Edit")); - -#ifndef QT_NO_CLIPBOARD - const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon("../../../../QtLing/images/cut.png")); - QAction *cutAct = new QAction(cutIcon, tr("Cu&t"), this); - - cutAct->setShortcuts(QKeySequence::Cut); - cutAct->setStatusTip(tr("Cut the current selection's contents to the " - "clipboard")); -// connect(cutAct, &QAction::triggered, textEdit, &QPlainTextEdit::cut); - editMenu->addAction(cutAct); - editToolBar->addAction(cutAct); - - const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon("../../../../QtLing/images/copy.png")); - QAction *copyAct = new QAction(copyIcon, tr("&Copy"), this); - copyAct->setShortcuts(QKeySequence::Copy); - copyAct->setStatusTip(tr("Copy the current selection's contents to the " - "clipboard")); -// connect(copyAct, &QAction::triggered, textEdit, &QPlainTextEdit::copy); - editMenu->addAction(copyAct); - editToolBar->addAction(copyAct); - - const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon("../../../../QtLing/images/paste.png")); - QAction *pasteAct = new QAction(pasteIcon, tr("&Paste"), this); - pasteAct->setShortcuts(QKeySequence::Paste); - pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current " - "selection")); -// connect(pasteAct, &QAction::triggered, textEdit, &QPlainTextEdit::paste); - editMenu->addAction(pasteAct); - editToolBar->addAction(pasteAct); - - menuBar()->addSeparator(); - -#endif // !QT_NO_CLIPBOARD - - QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); - QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &MainWindow::about); - aboutAct->setStatusTip(tr("Show the application's About box")); - - QAction *aboutQtAct = helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); - aboutQtAct->setStatusTip(tr("Show the Qt library's About box")); - - - //fileToolBar->addButton("Sort"); -} void MainWindow::createStatusBar() { statusBar()->showMessage(tr("Ready")); diff --git a/QtLing/mainwindow.h b/QtLing/mainwindow.h index adf9cfe..88ffbcf 100644 --- a/QtLing/mainwindow.h +++ b/QtLing/mainwindow.h @@ -38,6 +38,8 @@ class LxaStandardItemModel; class UpperTableView; class LowerTableView; class LeftSideTreeView; +class MainMenuBar; +class FindDockWidget; QT_END_NAMESPACE @@ -74,6 +76,8 @@ class MainWindow : public QMainWindow friend class LowerTableView; friend class lxaWindow; friend class CLexicon; + friend class MainMenu; + friend class MainMenuBar; QList m_lexicon_list; CLexicon* m_my_lexicon; @@ -103,7 +107,28 @@ class MainWindow : public QMainWindow QGroupBox * horizontalGroupBox; QGroupBox * verticalGroupBox; QString curFile; + FindDockWidget * m_find_dock_widget; + // ACTIONS, see mainwindow_actions.cpp + MainMenuBar * m_main_menu_bar; + QAction * openAct; + QAction * saveAsAct; + QAction * exitAct; + + QAction * cutAct; + QAction * copyAct; + QAction * pasteAct; + QAction * findAct; + QAction * findLeftAct; + QAction * findRightAct; + + QAction * aboutAct; + QAction * aboutQtAct; + + QAction * importAct; + QAction * evalAct; + QAction * importMorfessorAct; + QAction * evalMorfessorAct; public: @@ -125,11 +150,14 @@ class MainWindow : public QMainWindow void read_corpus(); // void set_up_graphics_scene_and_view(); - - protected: void closeEvent(QCloseEvent *event) override; +signals: + void xml_parsed(); + void lexicon_ready(); + void morfessor_parsed(); + private slots: void about(); @@ -146,6 +174,15 @@ private slots: void read_stems_and_words(); void write_stems_and_words(); + // for find functionality + void launch_find_dock(); + + // test for gold standard + void gs_read_and_parse_xml(); + void gs_evaluate(); + void read_morfessor_txt_file(); + void gs_evaluate_morfessor(); + #ifndef QT_NO_SESSIONMANAGER void commitData(QSessionManager &); #endif @@ -159,6 +196,8 @@ private slots: void read_dx1_file(); void read_text_file(); + void createFindDockWidget(UpperTableView* p_tableview); + // Qt-style modesl void load_stem_model(); void load_affix_model(); @@ -174,6 +213,10 @@ private slots: void load_subsignature_model(); void load_word_model(); void create_or_update_TreeModel(CLexicon* lexicon); + // update tree model after gs evaluation is completed + void append_eval_results(EvaluationResults& results, QStandardItem* parent_item); + void update_TreeModel_for_gs(CLexicon* lexicon); + void update_TreeModel_for_eval(CLexicon* lexicon); void sort_upper_table(); diff --git a/QtLing/mainwindow_actions.cpp b/QtLing/mainwindow_actions.cpp new file mode 100644 index 0000000..2714a0d --- /dev/null +++ b/QtLing/mainwindow_actions.cpp @@ -0,0 +1,127 @@ +#include "mainwindow.h" +#include +#include +#include +#include +#include "mainwindow_menubar.h" + +/* mainwindow_actions.cpp: + * contains the createActions() function that is used in the constructor + * function of the MainWindow object. Initiates QActions such as open, saveAs, + * evaluation, etc. Pointers to these actions are stored as member variables in + * the MainWindow class (see mainwindow.h:114-131) so that these actions may + * be used in different parts of the main window, including the menubar and + * toolbar. The implementation of the menubar is separated from this part of + * code to a newly defined class called MainMenuBar (see + * mainwindow_menubar.h/cpp). + */ + +void MainWindow::createActions() +{ + /* --- FILE --- */ + // Open + const QIcon openIcon = QIcon::fromTheme("document-open", QIcon("../../../../QtLing/images/open.png")); + openAct = new QAction(openIcon, tr("&Open..."), this); + openAct->setShortcuts(QKeySequence::Open); + openAct->setStatusTip(tr("Open an existing file")); + connect(openAct, &QAction::triggered, this, &MainWindow::ask_for_filename); + + // SaveAs + const QIcon saveAsIcon = QIcon::fromTheme("document-save-as"); + saveAsAct = new QAction(saveAsIcon, tr("Save &As ..."), this); + connect(saveAsAct, &QAction::triggered, this, &MainWindow::saveAs); + saveAsAct->setShortcuts(QKeySequence::SaveAs); + saveAsAct->setStatusTip(tr("Save the document under a new name")); + + // Exit + const QIcon exitIcon = QIcon::fromTheme("application-exit"); + exitAct = new QAction(exitIcon, tr("E&xit"), this); + connect(exitAct, &QAction::triggered, this, &QWidget::close); + exitAct->setShortcuts(QKeySequence::Quit); + exitAct->setStatusTip(tr("Exit the application")); + + /* --- EDIT --- */ + // Cut + const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon("../../../../QtLing/images/cut.png")); + cutAct = new QAction(cutIcon, tr("Cu&t"), this); + cutAct->setShortcuts(QKeySequence::Cut); + cutAct->setStatusTip(tr("Cut the current selection's contents to the " + "clipboard")); + + // Copy + const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon("../../../../QtLing/images/copy.png")); + copyAct = new QAction(copyIcon, tr("&Copy"), this); + copyAct->setShortcuts(QKeySequence::Copy); + copyAct->setStatusTip(tr("Copy the current selection's contents to the " + "clipboard")); + + // Paste + const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon("../../../../QtLing/images/paste.png")); + pasteAct = new QAction(pasteIcon, tr("&Paste"), this); + pasteAct->setShortcuts(QKeySequence::Paste); + pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current " + "selection")); + + // Find + findAct = new QAction(tr("&Find in both tables"), this); + connect(findAct, &QAction::triggered, this, &MainWindow::launch_find_dock); + findAct->setShortcuts(QKeySequence::Find); + findAct->setStatusTip(tr("Search for a string in the two upper tables")); + + findLeftAct = new QAction(tr("Find in upper-&Left table"), this); + connect(findLeftAct, &QAction::triggered, this, &MainWindow::launch_find_dock); + findAct->setStatusTip(tr("Search for a string in the upper-left table")); + + findRightAct = new QAction(tr("Find in upper-&Right table"), this); + connect(findRightAct, &QAction::triggered, this, &MainWindow::launch_find_dock); + findAct->setStatusTip(tr("Search for a string in the upper-right table")); + + + /* --- HELP --- */ + // About + aboutAct = new QAction(tr("&About"), this); + connect(aboutAct, &QAction::triggered, this, &MainWindow::about); + aboutAct->setStatusTip(tr("Show the application's About box")); + + // About Qt + aboutQtAct = new QAction(tr("About &Qt"), this); + connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt); + aboutQtAct->setStatusTip(tr("Show the Qt library's About box")); + + /* --- EVALUATE --- */ + // Import Gold Standard + importAct = new QAction(tr("Import &Gold Standard XML file"), this); + connect(importAct, &QAction::triggered, this, &MainWindow::gs_read_and_parse_xml); + importAct->setStatusTip(tr("Import and Load Gold Standard XML file")); + + // Evaluate Gold Standard XML + evalAct = new QAction(tr("&Evaluate current lexicon using Gold Standard"), this); + connect(evalAct, &QAction::triggered, this, &MainWindow::gs_evaluate); + evalAct->setStatusTip(tr("Evaluate output of linguistica using Gold Standard")); + evalAct->setDisabled(true); + + // Import Morfessor XML + importMorfessorAct = new QAction(tr("Import &Morfessor parses"), this); + connect(importMorfessorAct, &QAction::triggered, this, &MainWindow::read_morfessor_txt_file); + importMorfessorAct->setStatusTip(tr("Import Morfessor output file of word parses in txt format")); + + // Evaluate Morfessor XML + evalMorfessorAct = new QAction(tr("Evaluate Morfessor parses"), this); + connect(evalMorfessorAct, &QAction::triggered, this, &MainWindow::gs_evaluate_morfessor); + evalMorfessorAct->setStatusTip(tr("Evaluate imported parses using gold standard")); + evalMorfessorAct->setDisabled(true); + + // Create Menu Bar + m_main_menu_bar = new MainMenuBar(this); + setMenuBar(m_main_menu_bar); + + // Create Tool Bar + QToolBar* fileToolBar = addToolBar(tr("File")); + fileToolBar->addAction(openAct); + QToolBar* editToolBar = addToolBar(tr("Edit")); + editToolBar->addAction(cutAct); + editToolBar->addAction(copyAct); + editToolBar->addAction(pasteAct); + + +} diff --git a/QtLing/mainwindow_find.cpp b/QtLing/mainwindow_find.cpp new file mode 100644 index 0000000..9af52ab --- /dev/null +++ b/QtLing/mainwindow_find.cpp @@ -0,0 +1,242 @@ +#include "mainwindow_find.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mainwindow.h" + +FindDockWidget::FindDockWidget(MainWindow* p_main_window) +{ + setAllowedAreas(Qt::BottomDockWidgetArea); + setFeatures(QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetVerticalTitleBar); + m_parent_window = p_main_window; + m_child_dialog = new FindDialog(p_main_window); + + connect(m_child_dialog->m_button_close, SIGNAL(clicked(bool)), + this, SLOT(close())); + setWidget(m_child_dialog); +} + +void FindDockWidget::closeEvent(QCloseEvent *event) +{ + m_child_dialog->do_clear_search(); + event->accept(); +} + +FindDialog::FindDialog(MainWindow *p_main_window): + m_search_range(FindDialog::e_tableview_upper_all), + m_mainwindow(p_main_window), + m_label_items_found(NULL) +{ + m_layout = new QHBoxLayout(this); + + m_label_selection = new QLabel(tr("Find in:"), this); + + m_combobox_selection = new QComboBox(this); + m_combobox_selection->addItem(tr("Both upper tables")); + m_combobox_selection->addItem(tr("Upper-left table")); + m_combobox_selection->addItem(tr("Upper-right table")); + + int search_sel_init_index; + switch (m_search_range) { + case FindDialog::e_tableview_upper_all: + search_sel_init_index = 0; break; + case FindDialog::e_tableview_upper_left: + search_sel_init_index = 1; break; + case FindDialog::e_tableview_upper_right: + search_sel_init_index = 2; break; + default: + qDebug() << "mainwindow_find.cpp: FindDialog() - invalid search range!"; + } + m_checkbox_exact_match = new QCheckBox(tr("Exact Match?"),this); + m_checkbox_exact_match->setChecked(false); + + m_line_edit = new QLineEdit(this); + m_line_edit->setMaximumWidth(300); + + m_label_items_found = new QLabel(this); + + m_button_find_next = new QPushButton(tr("Find Next")); + + m_button_find_prev = new QPushButton(tr("Find Previous")); + + m_button_clear_search = new QPushButton(tr("Clear Search")); + + //-- implementing close button + // getting standard "x" icon fron style + QStyle* style = qApp->style(); + QIcon close_icon = style->standardIcon(QStyle::SP_TitleBarCloseButton); + m_button_close = new QPushButton(this); + m_button_close->setIcon(close_icon); + + m_layout->addWidget(m_label_selection); + m_layout->addWidget(m_combobox_selection); + m_layout->addWidget(m_checkbox_exact_match); + m_layout->addWidget(m_line_edit); + m_layout->addWidget(m_label_items_found); + m_label_items_found->hide(); + m_layout->addWidget(m_button_find_next); + m_layout->addWidget(m_button_find_prev); + m_layout->addWidget(m_button_clear_search); + m_layout->addWidget(m_button_close); + m_layout->addStretch(); + + setLayout(m_layout); + connect_button_signals(); + connect_search_signals(); +} + +void FindDialog::connect_button_signals() +{ + connect(m_combobox_selection, SIGNAL(currentIndexChanged(QString)), this, SLOT(change_search_range(QString))); + connect(m_line_edit, SIGNAL(returnPressed()), this, SLOT(do_next_search())); + connect(m_button_find_next, SIGNAL(clicked(bool)), this, SLOT(do_next_search())); + connect(m_button_find_prev, SIGNAL(clicked(bool)), this, SLOT(do_prev_search())); + connect(m_button_clear_search, SIGNAL(clicked(bool)), this, SLOT(do_clear_search())); + connect(m_line_edit, SIGNAL(textChanged(QString)), this, SLOT(do_clear_search())); +} + +void FindDialog::connect_search_signals() +{ + connect(this, SIGNAL(search_for_left_next(QString&)), + m_mainwindow->get_upper_left_tableview(), + SLOT(find_next_and_highlight(QString&))); + connect(this, SIGNAL(search_for_left_prev(QString&)), + m_mainwindow->get_upper_left_tableview(), + SLOT(find_prev_and_highlight(QString&))); + connect(m_mainwindow->get_upper_left_tableview(), + SIGNAL(num_items_found(int)), + this, SLOT(item_found(int))); + connect(this, SIGNAL(clear_left_search()), + m_mainwindow->get_upper_left_tableview(), + SLOT(clear_search())); + + connect(this, SIGNAL(search_for_right_next(QString&)), + m_mainwindow->get_upper_right_tableview(), + SLOT(find_next_and_highlight(QString&))); + connect(this, SIGNAL(search_for_right_prev(QString&)), + m_mainwindow->get_upper_right_tableview(), + SLOT(find_prev_and_highlight(QString&))); + connect(m_mainwindow->get_upper_right_tableview(), + SIGNAL(num_items_found(int)), + this, SLOT(item_found(int))); + connect(this, SIGNAL(clear_right_search()), + m_mainwindow->get_upper_right_tableview(), + SLOT(clear_search())); +} + +void FindDialog::change_search_range(QString s) +{ + // qDebug() << "FindDialog::change_search_range: received change search range signal - " << s; + if (s == "Both upper tables") + m_search_range = e_tableview_upper_all; + else if (s == "Upper-left table") + m_search_range = e_tableview_upper_left; + else if (s == "Upper-right table") + m_search_range = e_tableview_upper_right; + else + qDebug() << "FindDialog::change_search_range: wrong text in combobox!"; +} + +void FindDialog::do_next_search() +{ + QString search_text = m_line_edit->text(); + switch (m_search_range) { + case e_tableview_upper_all: + emit search_for_right_next(search_text); + case e_tableview_upper_left: + emit search_for_left_next(search_text); + break; + case e_tableview_upper_right: + emit search_for_right_next(search_text); + break; + default: + qDebug() << "mainwindow_find.cpp: connect_signals() - invalid search range!"; + } + + //qDebug() << "FindDialog: search_for_next(" + search_text + ") emitted!"; +} + +void FindDialog::do_prev_search() +{ + QString search_text = m_line_edit->text(); + switch (m_search_range) { + case e_tableview_upper_all: + emit search_for_right_prev(search_text); + case e_tableview_upper_left: + emit search_for_left_prev(search_text); + break; + case e_tableview_upper_right: + emit search_for_right_prev(search_text); + break; + default: + qDebug() << "mainwindow_find.cpp: connect_signals() - invalid search range!"; + } + //qDebug() << "FindDialog: search_for_prev(" + search_text + ") emitted!"; +} + +void FindDialog::do_clear_search() +{ + m_label_items_found->hide(); + if (sender() == m_button_clear_search) { + m_line_edit->setText(QString()); + emit clear_left_search(); + emit clear_right_search(); + } else if (sender() == m_line_edit) { + switch (m_search_range) { + case e_tableview_upper_all: + emit clear_right_search(); + case e_tableview_upper_left: + emit clear_left_search(); + return; + case e_tableview_upper_right: + emit clear_right_search(); + return; + default: + qDebug() << "mainwindow_find.cpp:do_clear_search() - invalid search range!"; + } + } else { + emit clear_left_search(); + emit clear_right_search(); + } +} + +void FindDialog::item_found(int n) +{ + //qDebug() << "item_found slot recieved signal with int n =" << n; + if (sender() == m_mainwindow->get_upper_left_tableview()) + m_left_items_found = n; + if (sender() == m_mainwindow->get_upper_right_tableview()) + m_right_items_found = n; + + QString label; + switch (m_search_range) { + case e_tableview_upper_all: + label = QString("Occurrences found: [Left] %1, [Right] %2") + .arg(m_left_items_found).arg(m_right_items_found); + break; + case e_tableview_upper_left: + label = QString("Occurrences found: [Left] %1") + .arg(m_left_items_found); + break; + case e_tableview_upper_right: + label = QString("Occurrences found: [Right] %2") + .arg(m_right_items_found); + break; + default: + qDebug() << "mainwindow_find.cpp: FindDialog::item_found - wrong switch case!"; + } + + m_label_items_found->setText(label); + m_label_items_found->show(); + +} diff --git a/QtLing/mainwindow_find.h b/QtLing/mainwindow_find.h new file mode 100644 index 0000000..703ab1e --- /dev/null +++ b/QtLing/mainwindow_find.h @@ -0,0 +1,99 @@ +#ifndef MAINWINDOW_FINDDOCKWIDGET_H +#define MAINWINDOW_FINDDOCKWIDGET_H + +#include +#include +#include +#include +#include +#include +#include "table_views.h" + +class FindDialog; +class MainWindow; +class QCloseEvent; + +/*! + * \brief The FindDockWidget class. A wrapper object that contains a FindDialog. + * + * A QDockWidget is a small window-like interface that can be attached to the + * margins of a QMainWindow object. + */ +class FindDockWidget : public QDockWidget +{ + Q_OBJECT + MainWindow* m_parent_window; + FindDialog* m_child_dialog; +public: + FindDockWidget(MainWindow* p_main_window); + FindDialog* get_child_dialog() { return m_child_dialog; } +public slots: + void closeEvent(QCloseEvent *event); + +}; + +#endif // MAINWINDOW_FINDDOCKWIDGET_H + +/*! + * \brief The FindDialog class. The main widget for implementing the search + * functionality. + * + * The FindDialog object detects if its interactive components (buttons, etc.) + * are activated through detecting signals from them to the FindDialog object's + * slots. These slots then send signals to the UpperTableView objects so that + * the TableView objects change their appearance in accordance with the search + * request. + */ +class FindDialog : public QWidget +{ + Q_OBJECT + friend class FindDockWidget; + typedef enum SearchRange { + e_tableview_upper_all, + e_tableview_upper_left, + e_tableview_upper_right + } SearchRange; + + QHBoxLayout* m_layout; + + SearchRange m_search_range; + MainWindow* m_mainwindow; + + QLabel* m_label_selection; + QComboBox* m_combobox_selection; + QCheckBox* m_checkbox_exact_match; + QLineEdit* m_line_edit; + QLabel* m_label_items_found; + QPushButton* m_button_find_next; + QPushButton* m_button_find_prev; + QPushButton* m_button_clear_search; + QPushButton* m_button_close; + + + int m_left_items_found; + int m_right_items_found; + +private: + void connect_button_signals(); + void connect_search_signals(); + +public: + FindDialog(MainWindow* p_main_window); + void set_search_selection(QString s) {m_combobox_selection->setCurrentText(s);} + bool is_exact_match() { return m_checkbox_exact_match->isChecked(); } + +signals: + void search_for_left_next(QString& s); + void search_for_right_next(QString& s); + void search_for_left_prev(QString& s); + void search_for_right_prev(QString& s); + void clear_left_search(); + void clear_right_search(); +public slots: + void change_search_range(QString); + void do_next_search(); + void do_prev_search(); + void item_found(int n); + void do_clear_search(); + +}; diff --git a/QtLing/mainwindow_finddockwidget.h.autosave b/QtLing/mainwindow_finddockwidget.h.autosave new file mode 100644 index 0000000..0da0206 --- /dev/null +++ b/QtLing/mainwindow_finddockwidget.h.autosave @@ -0,0 +1,17 @@ +#ifndef MAINWINDOW_FINDDOCKWIDGET_H +#define MAINWINDOW_FINDDOCKWIDGET_H + +#include +#include +#include +#include "table_views.h" + +class FindDockWidget : public QDockWidget +{ + UpperTableView* m_tableview_searched; + +public: + FindDockWidget(UpperTableView* tableview_searched = NULL); +}; + +#endif // MAINWINDOW_FINDDOCKWIDGET_H \ No newline at end of file diff --git a/QtLing/mainwindow_findwidget.cpp.autosave b/QtLing/mainwindow_findwidget.cpp.autosave new file mode 100644 index 0000000..a0e4c30 --- /dev/null +++ b/QtLing/mainwindow_findwidget.cpp.autosave @@ -0,0 +1,11 @@ +#include "mainwindow_findwidget.h" + +FindDockWidget::FindDockWidget(UpperTableView* tableview_searched): + m_tableview_searched(tableview_searched) +{ + setAllowedAreas(Qt::BottomDockWidgetArea); + setFeatures(QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable); + +} diff --git a/QtLing/mainwindow_menu_copy.cpp b/QtLing/mainwindow_menu_copy.cpp new file mode 100644 index 0000000..eb76417 --- /dev/null +++ b/QtLing/mainwindow_menu_copy.cpp @@ -0,0 +1,186 @@ +#include +#include + +#include +#include +#include + +#include "mainwindow.h" +#include "mainwindow_menu_copy.h" + +MainMenuBar::MainMenuBar(MainWindow *parent) : QObject(parent) +{ + // ------------------------------- + // --- File menu and toolbar + // ------------------------------- + gs_is_ready = false; + lexicon_is_ready = false; + eval_parse_is_ready = false; + fileMenu = addMenu(tr("&File")); + + // need to move this away + /*## + fileToolBar = parent->addToolBar(tr("File")); + + */ + +//---------------------> --------------------------> --------------------------> +// QAction * suffix_signature_display_action = new QAction(); +// CLexicon* lexicon = get_lexicon(); +// connect(suffix_signature_display_action, &QAction::triggered, this, &MainWindow::display_suffix_signatures ); + +// QAction * prefix_signature_display_action = new QAction(); +// connect(prefix_signature_display_action, &QAction::triggered, this, &MainWindow::display_prefix_signatures ); +//---------------------> --------------------------> --------------------------> + + + // Give a data file name, store the name, and read the file. + //# const QIcon openIcon = QIcon::fromTheme("document-open", QIcon("../../../../QtLing/images/open.png")); + openAct = new QAction(/*# openIcon, */tr("&Open..."), this); + openAct->setShortcuts(QKeySequence::Open); + openAct->setStatusTip(tr("Open an existing file")); + connect(openAct, &QAction::triggered, parent, &MainWindow::ask_for_filename); + fileMenu->addAction(openAct); + //# fileToolBar->addAction(openAct); + + // No action associated with this yet. + //# const QIcon saveAsIcon = QIcon::fromTheme("document-save-as"); + saveAsAct = new QAction(/*# saveAsIcon, */tr("Save &As ..."), this); + connect(saveAsAct, &QAction::triggered, parent, &MainWindow::saveAs); + saveAsAct->setShortcuts(QKeySequence::SaveAs); + saveAsAct->setStatusTip(tr("Save the document under a new name")); + + fileMenu->addSeparator(); + + //# const QIcon exitIcon = QIcon::fromTheme("application-exit"); + exitAct = new QAction(/*# exitIcon, */tr("E&xit"), this); + connect(exitAct, &QAction::triggered, parent, &QWidget::close); + exitAct->setShortcuts(QKeySequence::Quit); + exitAct->setStatusTip(tr("Exit the application")); + + // ------------------------------- + // --- edit menu and toolbar + // ------------------------------- + editMenu = addMenu(tr("&Edit")); + //# editToolBar = parent->addToolBar(tr("Edit")); + +#ifndef QT_NO_CLIPBOARD + //# const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon("../../../../QtLing/images/cut.png")); + cutAct = new QAction(cutIcon, tr("Cu&t"), this); + + cutAct->setShortcuts(QKeySequence::Cut); + cutAct->setStatusTip(tr("Cut the current selection's contents to the " + "clipboard")); +// connect(cutAct, &QAction::triggered, textEdit, &QPlainTextEdit::cut); + editMenu->addAction(cutAct); + editToolBar->addAction(cutAct); + + const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon("../../../../QtLing/images/copy.png")); + copyAct = new QAction(copyIcon, tr("&Copy"), this); + copyAct->setShortcuts(QKeySequence::Copy); + copyAct->setStatusTip(tr("Copy the current selection's contents to the " + "clipboard")); +// connect(copyAct, &QAction::triggered, textEdit, &QPlainTextEdit::copy); + editMenu->addAction(copyAct); + editToolBar->addAction(copyAct); + + const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon("../../../../QtLing/images/paste.png")); + pasteAct = new QAction(pasteIcon, tr("&Paste"), this); + pasteAct->setShortcuts(QKeySequence::Paste); + pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current " + "selection")); +// connect(pasteAct, &QAction::triggered, textEdit, &QPlainTextEdit::paste); + editMenu->addAction(pasteAct); + editToolBar->addAction(pasteAct); + + parent->menuBar()->addSeparator(); + +#endif // !QT_NO_CLIPBOARD + // ------------------------------- + // --- Help menu + // ------------------------------- + helpMenu = parent->menuBar()->addMenu(tr("&Help")); + aboutAct = helpMenu->addAction(tr("&About"), parent, &MainWindow::about); + aboutAct->setStatusTip(tr("Show the application's About box")); + + aboutQtAct = helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); + aboutQtAct->setStatusTip(tr("Show the Qt library's About box")); + + + // beginning of experiment using goldstandard + + // ------------------------------- + // --- Evaluation Menu + // ------------------------------- + + importAct = new QAction(tr("Import &Gold Standard XML file"), this); + importAct->setStatusTip(tr("Import and Load Gold Standard XML file")); + + evalAct = new QAction(tr("&Evaluate current lexicon using Gold Standard"), this); + evalAct->setStatusTip(tr("Evaluate output of linguistica using Gold Standard")); + evalAct->setDisabled(true); + + importMorfessorAct = new QAction(tr("Import &Morfessor parses"), this); + importMorfessorAct->setStatusTip(tr("Import Morfessor output file of word parses in txt format")); + + evalMorfessorAct = new QAction(tr("Evaluate Morfessor parses"), this); + evalMorfessorAct->setStatusTip(tr("Evaluate imported parses using gold standard")); + evalMorfessorAct->setDisabled(true); + + connect(importAct, &QAction::triggered, parent, &MainWindow::gs_read_and_parse_xml); + connect(evalAct, &QAction::triggered, parent, &MainWindow::gs_evaluate); + connect(importMorfessorAct, &QAction::triggered, parent, &MainWindow::read_morfessor_txt_file); + connect(evalMorfessorAct, &QAction::triggered, parent, &MainWindow::gs_evaluate_morfessor); + + evalMenu = parent->menuBar()->addMenu(tr("&Evaluate")); + evalMenu->addAction(importAct); + evalMenu->addAction(evalAct); + evalMenu->addAction(importMorfessorAct); + evalMenu->addAction(evalMorfessorAct); + //editToolBar->addAction(importAct); + + // end of goldstandard experiment + + //fileToolBar->addButton("Sort"); +} + +void MainMenu::check_and_enable_evalAct() +{ + if (lexicon_is_ready && gs_is_ready) { + if (!evalAct->isEnabled()) { + evalAct->setEnabled(true); + } + } +} + +void MainMenu::check_and_enable_evalMorfessorAct() +{ + if (lexicon_is_ready && gs_is_ready && eval_parse_is_ready) { + if (!evalMorfessorAct->isEnabled()) { + evalMorfessorAct->setEnabled(true); + } + } +} + +void MainMenu::gs_ready() +{ + // qDebug() << 136 << "MainMenu.cpp: gs_loaded"; + gs_is_ready = true; + check_and_enable_evalAct(); + check_and_enable_evalMorfessorAct(); +} + +void MainMenu::lexicon_ready() +{ + lexicon_is_ready = true; + check_and_enable_evalAct(); + check_and_enable_evalMorfessorAct(); +} + +void MainMenu::eval_parse_ready() +{ + eval_parse_is_ready = true; + check_and_enable_evalMorfessorAct(); +} + + diff --git a/QtLing/mainwindow_menu_copy.h b/QtLing/mainwindow_menu_copy.h new file mode 100644 index 0000000..c08e929 --- /dev/null +++ b/QtLing/mainwindow_menu_copy.h @@ -0,0 +1,56 @@ +#ifndef MAINMENU_H +#define MAINMENU_H + +#include +#include "mainwindow.h" +#include +#include + +class MainMenuBar : public QMenuBar +{ + Q_OBJECT + + QMenu *fileMenu; + QToolBar *fileToolBar; + QAction *openAct; + QAction *saveAsAct; + QAction *exitAct; + + QMenu *editMenu; + QToolBar *editToolBar; + + QAction *cutAct; + QAction *copyAct; + QAction *pasteAct; + + QMenu *helpMenu; + + QAction *aboutAct; + QAction *aboutQtAct; + + QMenu *evalMenu; + + QAction *importAct; + QAction *evalAct; + QAction *importMorfessorAct; + QAction *evalMorfessorAct; + + bool gs_is_ready; + bool lexicon_is_ready; + bool eval_parse_is_ready; + + void check_and_enable_evalAct(); + void check_and_enable_evalMorfessorAct(); + +public: + explicit MainMenuBar(MainWindow *parent = nullptr); + +signals: + +public slots: + void gs_ready(); + void lexicon_ready(); + void eval_parse_ready(); +}; + +#endif // MAINMENU_H diff --git a/QtLing/mainwindow_menubar.cpp b/QtLing/mainwindow_menubar.cpp new file mode 100644 index 0000000..78d200f --- /dev/null +++ b/QtLing/mainwindow_menubar.cpp @@ -0,0 +1,80 @@ +#include "mainwindow_menubar.h" +#include "mainwindow.h" + +MainMenuBar::MainMenuBar() +{ + +} + +MainMenuBar::MainMenuBar(MainWindow *parent): + gs_is_ready(false), + lexicon_is_ready(false), + eval_parse_is_ready(false) +{ + setParent(parent); + m_parent_window = parent; + fileMenu = addMenu(tr("&File")); + fileMenu->addAction(parent->openAct); + fileMenu->addSeparator(); + fileMenu->addAction(parent->saveAsAct); + fileMenu->addAction(parent->exitAct); + + editMenu = addMenu(tr("&Edit")); +#ifndef QT_NO_CLIPBOARD + editMenu->addAction(parent->cutAct); + editMenu->addAction(parent->copyAct); + editMenu->addAction(parent->pasteAct); +#endif + editMenu->addAction(parent->findAct); + editMenu->addAction(parent->findLeftAct); + editMenu->addAction(parent->findRightAct); + + helpMenu = addMenu(tr("&Help")); + helpMenu->addAction(parent->aboutAct); + helpMenu->addAction(parent->aboutQtAct); + + evalMenu = addMenu(tr("&Evaluate")); + evalMenu->addAction(parent->importAct); + evalMenu->addAction(parent->evalAct); + evalMenu->addAction(parent->importMorfessorAct); + evalMenu->addAction(parent->evalMorfessorAct); +} + +void MainMenuBar::check_and_enable_evalAct() +{ + if (lexicon_is_ready && gs_is_ready) { + if (!m_parent_window->evalAct->isEnabled()) { + m_parent_window->evalAct->setEnabled(true); + } + } +} + +void MainMenuBar::check_and_enable_evalMorfessorAct() +{ + if (lexicon_is_ready && gs_is_ready && eval_parse_is_ready) { + if (!m_parent_window->evalMorfessorAct->isEnabled()) { + m_parent_window->evalMorfessorAct->setEnabled(true); + } + } +} + +void MainMenuBar::gs_ready() +{ + // qDebug() << 136 << "MainMenu.cpp: gs_loaded"; + gs_is_ready = true; + check_and_enable_evalAct(); + check_and_enable_evalMorfessorAct(); +} + +void MainMenuBar::lexicon_ready() +{ + lexicon_is_ready = true; + check_and_enable_evalAct(); + check_and_enable_evalMorfessorAct(); +} + +void MainMenuBar::eval_parse_ready() +{ + eval_parse_is_ready = true; + check_and_enable_evalMorfessorAct(); +} diff --git a/QtLing/mainwindow_menubar.h b/QtLing/mainwindow_menubar.h new file mode 100644 index 0000000..2d7d882 --- /dev/null +++ b/QtLing/mainwindow_menubar.h @@ -0,0 +1,36 @@ +#ifndef MAINWINDOW_MENUBAR_H +#define MAINWINDOW_MENUBAR_H + +#include +#include +#include + +class MainWindow; + +class MainMenuBar : public QMenuBar +{ + Q_OBJECT + MainWindow *m_parent_window; + QMenu *fileMenu; + QMenu *editMenu; + QMenu *helpMenu; + QMenu *evalMenu; + + bool gs_is_ready; + bool lexicon_is_ready; + bool eval_parse_is_ready; + + void check_and_enable_evalAct(); + void check_and_enable_evalMorfessorAct(); + +public: + MainMenuBar(); + MainMenuBar(MainWindow* parent); + +public slots: + void gs_ready(); + void lexicon_ready(); + void eval_parse_ready(); +}; + +#endif // MAINWINDOW_MENUBAR_H diff --git a/QtLing/mainwindow_search.cpp.autosave b/QtLing/mainwindow_search.cpp.autosave new file mode 100644 index 0000000..ab60aa7 --- /dev/null +++ b/QtLing/mainwindow_search.cpp.autosave @@ -0,0 +1,11 @@ +#include "mainwindow_search.h" + +FindDockWidget::FindDockWidget(UpperTableView* tableview_searched): + m_tableview_searched(tableview_searched) +{ + setAllowedAreas(Qt::BottomDockWidgetArea); + setFeatures(QDockWidget::DockWidgetClosable | + QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable); + +} diff --git a/QtLing/table_views.h b/QtLing/table_views.h index bb7d8ff..bf65401 100644 --- a/QtLing/table_views.h +++ b/QtLing/table_views.h @@ -13,6 +13,18 @@ class UpperTableView : public QTableView eSortStyle m_signature_sort_style; + int m_gold_standard_display_order; + + // for search functionality // + int find_all_strings(const QString& str, bool exact_match); + void clear_items_found(); + QList m_items_found; + int m_row_recently_selected; +signals: + void num_items_found(int); + // for search functionality // + + public: UpperTableView (); UpperTableView (MainWindow*, eSortStyle = DEFAULT); @@ -28,6 +40,12 @@ public slots: void ShowModelsUpperTableView(const QModelIndex& ); void display_this_affixes_signatures(const QModelIndex & index); + // for search functionality // + bool find_prev_and_highlight(QString& s); + bool find_next_and_highlight(QString& s); + void clear_search(); + // for search functionality // + signals: void please_display_this_signature(QString sig); }; diff --git a/QtLing/table_views_lower.cpp b/QtLing/table_views_lower.cpp index bb88b91..fae6860 100644 --- a/QtLing/table_views_lower.cpp +++ b/QtLing/table_views_lower.cpp @@ -233,6 +233,7 @@ LowerTableView::LowerTableView(MainWindow * window) LeftSideTreeView::LeftSideTreeView(MainWindow* window) { m_parent_window = window; + setEditTriggers(QAbstractItemView::NoEditTriggers); } diff --git a/QtLing/table_views_upper.cpp b/QtLing/table_views_upper.cpp index 12f5704..e8f8d0e 100644 --- a/QtLing/table_views_upper.cpp +++ b/QtLing/table_views_upper.cpp @@ -1,6 +1,8 @@ #include +#include #include "table_views.h" #include "lxamodels.h" +#include "mainwindow_find.h" @@ -13,9 +15,14 @@ UpperTableView::UpperTableView (MainWindow* window, eSortStyle this_sort_style) { m_parent_window = window; m_signature_sort_style = this_sort_style; - QFont sansFont("Ariel", 20); + + QFont sansFont("Ariel", 18); setFont(sansFont); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setSortingEnabled(true); + m_row_recently_selected = -2; + m_gold_standard_display_order = 0; } @@ -42,73 +49,352 @@ void UpperTableView::ShowModelsUpperTableView(const QModelIndex& index) { CLexicon* lexicon = m_parent_window->get_lexicon(); QString component; + eDataType curr_data_type; + + // -- used for displaying gold standard information + UpperTableView* left_table = m_parent_window->get_upper_left_tableview(); + UpperTableView* right_table = m_parent_window->get_upper_right_tableview(); + int& left_order = left_table->m_gold_standard_display_order; + int& right_order = right_table->m_gold_standard_display_order; + // -- + + bool is_child_of_gs = false; + if (index.isValid()){ component = index.data().toString(); + QString parent_component = index.parent().data().toString(); + if (parent_component == "Gold Standard") + is_child_of_gs = true; + } else return; + + // Show gold standard information in tables + if (is_child_of_gs && (component == "True Positive Parses" + || component == "Gold Standard Parses" + || component == "Retrieved Parses" + || component == "Gold Standard Words")) { + if (component == "True Positive Parses") { + curr_data_type = e_data_gs_true_positive_parses; + } else if (component == "Retrieved Parses") { + curr_data_type = e_data_gs_retrieved_parses; + } else if (component == "Gold Standard Parses") { + curr_data_type = e_data_gs_gold_standard_parses; + } else if (component == "Gold Standard Words") { + component = "Gold Standard Parses"; + curr_data_type = e_data_gs_gold_standard_parses; + } + set_data_type(curr_data_type); + + if (left_order == 0 && right_order == 0) { + setModel(m_parent_window->m_Models[component]); + if (this == right_table) + left_order = 1; + } else if (left_order == 1 && right_order == 0) { + if (this == right_table) { + //qDebug() << "Showing " << component << "on the right"; + setModel(m_parent_window->m_Models[component]); + left_order = 0; + right_order = 1; + } + } else if (left_order == 0 && right_order == 1) { + if (this == left_table) { + setModel(m_parent_window->m_Models[component]); + //qDebug() << "Showing " << component << "on the left"; + } + if (this == right_table) { + left_order = 1; + right_order = 0; + } + } else { + qDebug() << "UpperTableView::ShowModelsUpperTableView:" + "wrong case of gs display order!"; + left_order = 0; + right_order = 0; + } } - if (component == "Words"){ - setModel(m_parent_window->m_Models["Words"]); - set_data_type( e_data_words ); - } - else if (component == "Prefixal stems"){ - setModel(m_parent_window->m_Models["Prefixal stems"]); - set_data_type(e_prefixal_stems); - } - else if (component == "Suffixal stems"){ - setModel(m_parent_window->m_Models["Suffixal stems"]); - set_data_type(e_suffixal_stems); - } - else if (component == "Suffixes"){ - m_parent_window->display_suffixes(); - set_data_type(e_data_suffixes); + // showing information in other tables + else { + left_table->m_gold_standard_display_order = 0; + right_table->m_gold_standard_display_order = 0; + + if (component == "Words"){ + setModel(m_parent_window->m_Models["Words"]); + set_data_type( e_data_words ); + } + else if (component == "Prefixal stems"){ + setModel(m_parent_window->m_Models["Prefixal stems"]); + set_data_type(e_prefixal_stems); + } + else if (component == "Suffixal stems"){ + setModel(m_parent_window->m_Models["Suffixal stems"]); + set_data_type(e_suffixal_stems); + } + else if (component == "Prefixes"){ + m_parent_window->display_prefixes(); + set_data_type(e_data_prefixes); + } + else if (component == "Suffixes"){ + m_parent_window->display_suffixes(); + set_data_type(e_data_suffixes); + } + else if (component == "Signatures"){ + m_parent_window->display_suffix_signatures(lexicon); + } + else if (component == "EPositive signatures"){ + m_parent_window->display_epositive_suffix_signatures(lexicon); + } + else if (component == "Prefix signatures"){ + m_parent_window->display_prefix_signatures(lexicon); + } + else if (component == "EPositive prefix signatures"){ + m_parent_window->display_epositive_prefix_signatures(lexicon); + } + else if (component == "Signature graph edges"){ + m_parent_window->display_signature_graph_edges(lexicon); + } + else if (component == "Residual parasignatures"){ + + } + else if (component == "Parasuffixes"){ + setModel(m_parent_window->m_Models["Parasuffixes"]); + set_data_type ( e_data_suffixes ); + sortByColumn(1); + } + else if (component == "Singleton signatures"){ + + } + else if (component == "Passive signatures"){ + setModel(m_parent_window->m_Models["Passive signatures"]); + set_data_type ( e_data_hollow_suffixal_signatures ); + sortByColumn(1); } + else if (component == "Hypotheses"){ + m_parent_window->display_hypotheses(); + } + // add component 10 } - else if (component == "Prefixes"){ - m_parent_window->display_prefixes(); - set_data_type(e_data_prefixes); + + resizeColumnsToContents(); + + // Steps for making a sorted proxy model: + // Using the model that is already set as the base model for a QSortFilterProxyModel + // Reset proxy model as model of TableView + +} + +bool rows_less_than (const QStandardItem* item1, const QStandardItem* item2) +{ + return item1->row() < item2->row(); +} + +int UpperTableView::find_all_strings(const QString& str, bool exact_match) +{ + // qDebug() << "Finding strings"; + QStandardItemModel* p_model = (QStandardItemModel*) model(); + if (p_model == NULL) { + // qDebug() << "Model not loaded"; + return 0; } - else if (component == "Signatures"){ - m_parent_window->display_suffix_signatures(lexicon); + m_items_found = QList(); + m_items_found = p_model->findItems(str, exact_match? Qt::MatchExactly : Qt::MatchContains, 0); + int num_items_found = m_items_found.length(); + if (num_items_found == 0) { + // qDebug() << str << "was not found"; + return 0; + } else { + qSort(m_items_found.begin(), m_items_found.end(), rows_less_than); + //Debug() << "Found" << num_items_found << "occurrences of" << str; + QBrush brush_item_found(QColor(57, 197, 187)); + QStandardItem* p_item_found; + foreach (p_item_found, m_items_found) { + p_item_found->setBackground(brush_item_found); + } + return num_items_found; } - else if (component == "EPositive signatures"){ - m_parent_window->display_epositive_suffix_signatures(lexicon); +} + +void UpperTableView::clear_search() +{ + QBrush brush(QColor(255, 255, 255)); + QStandardItemModel* p_model = (QStandardItemModel*) model(); + for (int row_i = 0; row_i < p_model->rowCount(); row_i++) { + p_model->item(row_i)->setBackground(brush); } - else if (component == "Prefix signatures"){ - m_parent_window->display_prefix_signatures(lexicon); + m_items_found = QList(); + clearSelection(); + m_row_recently_selected = -2; +} + +bool UpperTableView::find_next_and_highlight(QString &s) +{ + // qDebug() << "Signal to find next received; string to find:" + s; + QStandardItemModel* p_model = (QStandardItemModel*) model(); + FindDialog* p_find_dialog = (FindDialog*) sender(); + + if (p_model == NULL) { + qDebug() << "Model not loaded"; + m_row_recently_selected = -2; + return false; } - else if (component == "EPositive prefix signatures"){ - m_parent_window->display_epositive_prefix_signatures(lexicon); + + int num_found = find_all_strings(s, p_find_dialog->is_exact_match()); + if (num_found == 0) { + emit num_items_found(0); + m_row_recently_selected = -2; + return false; } - else if (component == "Signature graph edges"){ - m_parent_window->display_signature_graph_edges(lexicon); + + emit num_items_found(num_found); + QModelIndexList sel_list = selectedIndexes(); + int sel_list_len = sel_list.length(); + int lowest_row; + // see if the user has made a selection in the tableview + if (sel_list_len != 0) { + // get lowest row number among the items selected + lowest_row = p_model->rowCount(); + QModelIndex index; + foreach (index, sel_list) { + int curr_row = index.row(); + if (curr_row < lowest_row) + lowest_row = curr_row; + } + m_row_recently_selected = lowest_row; + } else { + // The user did not select anything; start from the beginning + if (m_row_recently_selected == -2) + m_row_recently_selected = -1; } - else if (component == "Residual parasignatures"){ + // Highlight an item among the list of items found, this item may follow + // after the user's selection or may be the first item in that list + bool next_item_found = false; + QList::ConstIterator iter_item_found; + int curr_row; + for (iter_item_found = m_items_found.constBegin(); + iter_item_found != m_items_found.constEnd(); ) { + curr_row = (*iter_item_found)->row(); + if (curr_row > m_row_recently_selected) { + m_row_recently_selected = curr_row; + next_item_found = true; + break; + } + else + iter_item_found++; } - else if (component == "Parasuffixes"){ - setModel(m_parent_window->m_Models["Parasuffixes"]); - set_data_type ( e_data_suffixes ); - sortByColumn(1); + + if (next_item_found) { + selectRow(m_row_recently_selected); + scrollTo(p_model->index(m_row_recently_selected,0)); + return true; + } else { + QMessageBox restart; + if (this == m_parent_window->get_upper_left_tableview()) + restart.setText("Upper-left table: Search has reached the end."); + else if (this == m_parent_window->get_upper_right_tableview()) + restart.setText("Upper-right table: Search has reached the end."); + restart.setInformativeText("Start search again from the beginning?"); + restart.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + restart.setDefaultButton(QMessageBox::Yes); + int rec = restart.exec(); + switch (rec) { + case QMessageBox::Yes: + m_row_recently_selected = -2; + clearSelection(); + find_next_and_highlight(s); + break; + case QMessageBox::No: + break; + default: + qDebug() << "table_views_upper.cpp: find_next_and_highlight()" + "- wrong switch case!"; + } + return true; } - else if (component == "Singleton signatures"){ +} +bool UpperTableView::find_prev_and_highlight(QString &s) +{ + QStandardItemModel* p_model = (QStandardItemModel*) model(); + FindDialog* p_find_dialog = (FindDialog*) sender(); + + if (p_model == NULL) { + qDebug() << "Model not loaded"; + m_row_recently_selected = -2; + return false; } - else if (component == "Passive signatures"){ - setModel(m_parent_window->m_Models["Passive signatures"]); - set_data_type ( e_data_hollow_suffixal_signatures ); - sortByColumn(1); } - else if (component == "Hypotheses"){ - m_parent_window->display_hypotheses(); + + int num_found = find_all_strings(s, p_find_dialog->is_exact_match()); + if (num_found == 0) { + emit num_items_found(0); + m_row_recently_selected = -2; + return false; } - // add component 10 + emit num_items_found(num_found); + QModelIndexList sel_list = selectedIndexes(); + int sel_list_len = sel_list.length(); + int highest_row; + // see if the user has made a selection in the tableview + if (sel_list_len != 0) { + // get lowest row number among the items selected + highest_row = -1; + QModelIndex index; + foreach (index, sel_list) { + int curr_row = index.row(); + if (curr_row > highest_row) + highest_row = curr_row; + } + m_row_recently_selected = highest_row; + } else { + // The user did not select anything; start from the beginning + if (m_row_recently_selected == -2) + m_row_recently_selected = p_model->rowCount(); + } + // Highlight an item among the list of items found, this item may follow + // after the user's selection or may be the first item in that list + bool next_item_found = false; + QList::const_reverse_iterator iter_item_found; + int curr_row; + for (iter_item_found = m_items_found.crbegin(); + iter_item_found != m_items_found.crend(); ) { + curr_row = (*iter_item_found)->row(); + if (curr_row < m_row_recently_selected) { + m_row_recently_selected = curr_row; + next_item_found = true; + break; + } + else + iter_item_found++; + } - resizeColumnsToContents(); + if (next_item_found) { + selectRow(m_row_recently_selected); + scrollTo(p_model->index(m_row_recently_selected,0)); + return true; + } else { + QMessageBox restart; + restart.setText("Search has reached the top of the file."); + restart.setInformativeText("Start search again from the bottom?"); + restart.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + restart.setDefaultButton(QMessageBox::Yes); + int rec = restart.exec(); + switch (rec) { + case QMessageBox::Yes: + m_row_recently_selected = -2; + clearSelection(); + find_prev_and_highlight(s); + break; + case QMessageBox::No: + break; + default: + qDebug() << "table_views_upper.cpp: find_prev_and_highlight()" + "- wrong switch case!"; + } + return true; + } } - - /** * @brief UpperTableView::display_this_affixes_signatures * This is used when we click on the left upper view to identify an affix; @@ -150,4 +436,3 @@ void UpperTableView::display_this_affixes_signatures(const QModelIndex & index) get_parent_window()->get } */ -