wordless/wl_settings/wl_settings.py
# ----------------------------------------------------------------------
# Wordless: Settings - Settings
# Copyright (C) 2018-2024 Ye Lei (叶磊)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ----------------------------------------------------------------------
import os
import traceback
from PyQt5.QtCore import pyqtSignal, QCoreApplication, Qt
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import (
QAbstractItemView,
QHeaderView,
QMessageBox,
QPushButton,
QStackedWidget,
QTreeView,
QWidget
)
from wordless.wl_checks import wl_checks_misc, wl_checks_work_area
from wordless.wl_dialogs import wl_dialogs, wl_msg_boxes
from wordless.wl_widgets import wl_buttons, wl_layouts
_tr = QCoreApplication.translate
class Wl_Settings(wl_dialogs.Wl_Dialog):
wl_settings_changed = pyqtSignal()
def __init__(self, main):
super().__init__(
main,
title = _tr('Wl_Settings', 'Settings'),
width = 1024,
height = 768
)
# Avoid circular imports
from wordless.wl_settings import ( # pylint: disable=import-outside-toplevel
wl_settings_general,
wl_settings_files,
wl_settings_sentence_tokenization,
wl_settings_word_tokenization,
wl_settings_syl_tokenization,
wl_settings_pos_tagging,
wl_settings_lemmatization,
wl_settings_stop_word_lists,
wl_settings_dependency_parsing,
wl_settings_sentiment_analysis,
wl_settings_measures,
wl_settings_tables,
wl_settings_figs
)
self.tree_settings = QTreeView(self)
self.tree_settings.setModel(QStandardItemModel())
self.tree_settings.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.tree_settings.setHeaderHidden(True)
self.tree_settings.header().setSectionResizeMode(QHeaderView.ResizeToContents)
self.tree_settings.header().setStretchLastSection(False)
self.tree_settings.model().appendRow(QStandardItem(self.tr('General')))
self.tree_settings.model().item(0).appendRow(QStandardItem(self.tr('Import')))
self.tree_settings.model().item(0).appendRow(QStandardItem(self.tr('Export')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Files')))
self.tree_settings.model().item(1).appendRow(QStandardItem(self.tr('Tags')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Sentence Tokenization')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Word Tokenization')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Syllable Tokenization')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Part-of-speech Tagging')))
self.tree_settings.model().item(5).appendRow(QStandardItem(self.tr('Tagsets')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Lemmatization')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Stop Word Lists')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Dependency Parsing')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Sentiment Analysis')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Measures')))
self.tree_settings.model().item(10).appendRow(QStandardItem(self.tr('Readability')))
self.tree_settings.model().item(10).appendRow(QStandardItem(self.tr('Lexical Density/Diversity')))
self.tree_settings.model().item(10).appendRow(QStandardItem(self.tr('Dispersion')))
self.tree_settings.model().item(10).appendRow(QStandardItem(self.tr('Adjusted Frequency')))
self.tree_settings.model().item(10).appendRow(QStandardItem(self.tr('Statistical Significance')))
self.tree_settings.model().item(10).appendRow(QStandardItem(self.tr('Bayes Factor')))
self.tree_settings.model().item(10).appendRow(QStandardItem(self.tr('Effect Size')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Tables')))
self.tree_settings.model().item(11).appendRow(QStandardItem(self.tr('Concordancer')))
self.tree_settings.model().item(11).appendRow(QStandardItem(self.tr('Parallel Concordancer')))
self.tree_settings.model().item(11).appendRow(QStandardItem(self.tr('Dependency Parser')))
self.tree_settings.model().appendRow(QStandardItem(self.tr('Figures')))
self.tree_settings.model().item(12).appendRow(QStandardItem(self.tr('Line Charts')))
self.tree_settings.model().item(12).appendRow(QStandardItem(self.tr('Word Clouds')))
self.tree_settings.model().item(12).appendRow(QStandardItem(self.tr('Network Graphs')))
# Calculate width
for i in range(self.tree_settings.model().rowCount()):
self.tree_settings.setExpanded(self.tree_settings.model().item(i, 0).index(), True)
self.tree_settings.setFixedWidth(self.tree_settings.columnWidth(0) + 10)
for i in range(self.tree_settings.model().rowCount()):
self.tree_settings.setExpanded(self.tree_settings.model().item(i, 0).index(), False)
self.tree_settings.selectionModel().selectionChanged.connect(self.selection_changed)
self.stacked_widget_settings = QStackedWidget(self)
# General
self.settings_general = wl_settings_general.Wl_Settings_General(self.main)
self.settings_general_imp = wl_settings_general.Wl_Settings_General_Imp(self.main)
self.settings_general_exp = wl_settings_general.Wl_Settings_General_Exp(self.main)
# Files
self.settings_files = wl_settings_files.Wl_Settings_Files(self.main)
self.settings_files_tags = wl_settings_files.Wl_Settings_Files_Tags(self.main)
self.settings_sentence_tokenization = wl_settings_sentence_tokenization.Wl_Settings_Sentence_Tokenization(self.main)
self.settings_word_tokenization = wl_settings_word_tokenization.Wl_Settings_Word_Tokenization(self.main)
self.settings_syl_tokenization = wl_settings_syl_tokenization.Wl_Settings_Syl_Tokenization(self.main)
# Part-of-speech Tagging
self.settings_pos_tagging = wl_settings_pos_tagging.Wl_Settings_Pos_Tagging(self.main)
self.settings_pos_tagging_tagsets = wl_settings_pos_tagging.Wl_Settings_Pos_Tagging_Tagsets(self.main)
self.settings_lemmatization = wl_settings_lemmatization.Wl_Settings_Lemmatization(self.main)
self.settings_stop_words_lists = wl_settings_stop_word_lists.Wl_Settings_Stop_Word_Lists(self.main)
self.settings_dependency_parsing = wl_settings_dependency_parsing.Wl_Settings_Dependency_Parsing(self.main)
self.settings_sentiment_analysis = wl_settings_sentiment_analysis.Wl_Settings_Sentiment_Analysis(self.main)
# Measures
self.settings_measures_readability = wl_settings_measures.Wl_Settings_Measures_Readability(self.main)
self.settings_measures_lexical_density_diversity = wl_settings_measures.Wl_Settings_Measures_Lexical_Density_Diversity(self.main)
self.settings_measures_dispersion = wl_settings_measures.Wl_Settings_Measures_Dispersion(self.main)
self.settings_measures_adjusted_freq = wl_settings_measures.Wl_Settings_Measures_Adjusted_Freq(self.main)
self.settings_measures_statistical_significance = wl_settings_measures.Wl_Settings_Measures_Statistical_Significance(self.main)
self.settings_measures_bayes_factor = wl_settings_measures.Wl_Settings_Measures_Bayes_Factor(self.main)
self.settings_measures_effect_size = wl_settings_measures.Wl_Settings_Measures_Effect_Size(self.main)
# Tables
self.settings_tables = wl_settings_tables.Wl_Settings_Tables(self.main)
self.settings_tables_concordancer = wl_settings_tables.Wl_Settings_Tables_Concordancer(self.main)
self.settings_tables_parallel_concordancer = wl_settings_tables.Wl_Settings_Tables_Parallel_Concordancer(self.main)
self.settings_tables_dependency_parser = wl_settings_tables.Wl_Settings_Tables_Dependency_Parser(self.main)
# Figures
self.settings_figs_line_charts = wl_settings_figs.Wl_Settings_Figs_Line_Charts(self.main)
self.settings_figs_word_clouds = wl_settings_figs.Wl_Settings_Figs_Word_Clouds(self.main)
self.settings_figs_network_graphs = wl_settings_figs.Wl_Settings_Figs_Network_Graphs(self.main)
self.settings_all = {
self.tr('General'): self.settings_general,
self.tr('Import'): self.settings_general_imp,
self.tr('Export'): self.settings_general_exp,
self.tr('Files'): self.settings_files,
self.tr('Tags'): self.settings_files_tags,
self.tr('Sentence Tokenization'): self.settings_sentence_tokenization,
self.tr('Word Tokenization'): self.settings_word_tokenization,
self.tr('Syllable Tokenization'): self.settings_syl_tokenization,
self.tr('Part-of-speech Tagging'): self.settings_pos_tagging,
self.tr('Tagsets'): self.settings_pos_tagging_tagsets,
self.tr('Lemmatization'): self.settings_lemmatization,
self.tr('Stop Word Lists'): self.settings_stop_words_lists,
self.tr('Dependency Parsing'): self.settings_dependency_parsing,
self.tr('Sentiment Analysis'): self.settings_sentiment_analysis,
self.tr('Readability'): self.settings_measures_readability,
self.tr('Lexical Density/Diversity'): self.settings_measures_lexical_density_diversity,
self.tr('Dispersion'): self.settings_measures_dispersion,
self.tr('Adjusted Frequency'): self.settings_measures_adjusted_freq,
self.tr('Statistical Significance'): self.settings_measures_statistical_significance,
self.tr('Bayes Factor'): self.settings_measures_bayes_factor,
self.tr('Effect Size'): self.settings_measures_effect_size,
self.tr('Tables'): self.settings_tables,
self.tr('Concordancer'): self.settings_tables_concordancer,
self.tr('Parallel Concordancer'): self.settings_tables_parallel_concordancer,
self.tr('Dependency Parser'): self.settings_tables_dependency_parser,
self.tr('Line Charts'): self.settings_figs_line_charts,
self.tr('Word Clouds'): self.settings_figs_word_clouds,
self.tr('Network Graphs'): self.settings_figs_network_graphs
}
for settings in self.settings_all.values():
scroll_area_settings = wl_layouts.Wl_Scroll_Area(self)
scroll_area_settings.setWidget(settings)
settings.scroll_area_settings = scroll_area_settings
self.stacked_widget_settings.addWidget(scroll_area_settings)
button_reset_settings = wl_buttons.Wl_Button(self.tr('Reset all settings'), self)
button_save = QPushButton(self.tr('Save'), self)
button_apply = QPushButton(self.tr('Apply'), self)
button_cancel = QPushButton(self.tr('Cancel'), self)
button_reset_settings.setMinimumWidth(150)
button_reset_settings.clicked.connect(self.reset_all_settings)
button_save.clicked.connect(self.save_settings)
button_apply.clicked.connect(self.apply_settings)
button_cancel.clicked.connect(self.reject)
layout_buttons = wl_layouts.Wl_Layout()
layout_buttons.addWidget(button_reset_settings, 0, 0)
layout_buttons.addWidget(button_save, 0, 2)
layout_buttons.addWidget(button_apply, 0, 3)
layout_buttons.addWidget(button_cancel, 0, 4)
layout_buttons.setColumnStretch(1, 1)
self.setLayout(wl_layouts.Wl_Layout())
self.layout().addWidget(self.tree_settings, 0, 0)
self.layout().addWidget(self.stacked_widget_settings, 0, 1)
self.layout().addLayout(layout_buttons, 1, 0, 1, 2)
self.tree_settings.node_selected_old = self.tree_settings.model().item(0)
def selection_changed(self, selected, deselected): # pylint: disable=unused-argument
if self.tree_settings.selectionModel().selectedIndexes():
if self.validate_settings():
i_selected = self.tree_settings.selectionModel().currentIndex()
node_selected = self.tree_settings.model().itemFromIndex(i_selected)
node_selected_text = node_selected.text()
if node_selected.hasChildren():
self.tree_settings.setExpanded(i_selected, True)
for i, node_text in enumerate(self.settings_all):
if node_selected_text == node_text:
self.stacked_widget_settings.setCurrentIndex(i)
self.tree_settings.node_selected_old = node_selected
self.main.settings_custom['settings']['node_cur'] = node_selected_text
# Delay loading of POS tag mappings
if node_selected_text == self.tr('Tagsets') and not self.settings_pos_tagging_tagsets.pos_tag_mappings_loaded:
self.settings_pos_tagging_tagsets.combo_box_tagsets_lang.currentTextChanged.emit(self.settings_pos_tagging_tagsets.combo_box_tagsets_lang.currentText())
self.settings_pos_tagging_tagsets.pos_tag_mappings_loaded = True
else:
self.tree_settings.selectionModel().blockSignals(True)
self.tree_settings.clearSelection()
self.tree_settings.setCurrentIndex(self.tree_settings.model().findItems(self.main.settings_custom['settings']['node_cur'], Qt.MatchRecursive)[0].index())
self.tree_settings.selectionModel().blockSignals(False)
def load_settings(self, defaults = False):
for settings in self.settings_all.values():
settings.load_settings(defaults = defaults)
def validate_settings(self):
for settings in self.settings_all.values():
if not settings.validate_settings():
return False
return True
def reset_all_settings(self):
if wl_msg_boxes.wl_msg_box_question(
main = self.main,
title = self.tr('Reset All Settings'),
text = self.tr('''
<div>Do you want to reset all settings to their defaults?</div>
<br>
<div><b>Warning: This will affect settings on all pages!</b></div>
''')
):
self.load_settings(defaults = True)
self.activateWindow()
def save_settings(self):
if self.apply_settings():
self.accept()
def apply_settings(self):
if self.validate_settings():
for settings in self.settings_all.values():
if not settings.apply_settings():
return False
self.wl_settings_changed.emit()
return True
else:
return False
def load(self, node = None):
try:
self.load_settings()
self.tree_settings.clearSelection()
if node:
self.tree_settings.setCurrentIndex(self.tree_settings.model().findItems(node, Qt.MatchRecursive)[0].index())
else:
self.tree_settings.setCurrentIndex(self.tree_settings.model().findItems(self.main.settings_custom['settings']['node_cur'], Qt.MatchRecursive)[0].index())
# Expand current node
node_cur = self.tree_settings.model().itemFromIndex(self.tree_settings.selectionModel().currentIndex())
self.tree_settings.setExpanded(node_cur.index(), True)
if node_cur.parent():
self.tree_settings.setExpanded(node_cur.parent().index(), True)
self.exec_()
except Exception: # pylint: disable=broad-exception-caught
wl_checks_work_area.check_err(self.main, traceback.format_exc())
# self.tr() does not work in inherited classes
class Wl_Settings_Node(QWidget):
def __init__(self, main):
super().__init__()
self.main = main
def wl_msg_box_path_empty(self):
wl_msg_boxes.Wl_Msg_Box_Warning(
self.main,
title = _tr('Wl_Settings_Node', 'Empty Path'),
text = _tr('Wl_Settings_Node', '''
<div>The path should not be left empty!</div>
'''),
).open()
def wl_msg_box_path_not_found(self, path):
wl_msg_boxes.Wl_Msg_Box_Warning(
self.main,
title = _tr('Wl_Settings_Node', 'Path not Found'),
text = _tr('Wl_Settings_Node', '''
<div>The specified path "{}" could not be found!</div>
<div>Please check your settings and try again.</div>
''').format(path),
).open()
def wl_msg_box_path_is_dir(self, path):
wl_msg_boxes.Wl_Msg_Box_Warning(
self.main,
title = _tr('Wl_Settings_Node', 'Invalid File Path'),
text = _tr('Wl_Settings_Node', '''
<div>The specified path "{}" should be a file, not a directory!</div>
<div>Please check your settings and try again.</div>
''').format(path),
).open()
def wl_msg_box_path_not_dir(self, path):
wl_msg_boxes.Wl_Msg_Box_Warning(
self.main,
title = _tr('Wl_Settings_Node', 'Invalid Directory Path'),
text = _tr('Wl_Settings_Node', '''
<div>The specified path "{}" should be a directory, not a file!</div>
<div>Please check your settings and try again.</div>
''').format(path),
).open()
def validate_path_file(self, line_edit):
path = line_edit.text().strip()
path_ok = True
if path:
if not os.path.exists(path):
self.wl_msg_box_path_not_found(path)
path_ok = False
elif os.path.isdir(path):
self.wl_msg_box_path_is_dir(path)
path_ok = False
else:
self.wl_msg_box_path_empty()
path_ok = False
if not path_ok:
line_edit.setFocus()
line_edit.selectAll()
return path_ok
def validate_path_dir(self, line_edit):
path = line_edit.text().strip()
path_ok = True
if path:
if not os.path.exists(path):
self.wl_msg_box_path_not_found(path)
path_ok = False
elif not os.path.isdir(path):
self.wl_msg_box_path_not_dir(path)
path_ok = False
else:
self.wl_msg_box_path_empty()
path_ok = False
if not path_ok:
line_edit.setFocus()
line_edit.selectAll()
return path_ok
def confirm_path(self, line_edit):
path = line_edit.text().strip()
path_ok = True
if path:
if not os.path.exists(path):
reply = QMessageBox.question(
self.main,
_tr('Wl_Settings_Node', 'Path Not Exist'),
_tr('Wl_Settings_Node', '''
{}
<body>
<div>The specified path "{}" does not exist.</div>
<div>Do you want to create the directory?</div>
</body>
''').format(self.main.settings_global['styles']['style_dialog'], path),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.Yes:
wl_checks_misc.check_dir(path)
else:
path_ok = False
elif not os.path.isdir(path):
self.wl_msg_box_path_not_dir(path)
path_ok = False
else:
self.wl_msg_box_path_empty()
path_ok = False
if not path_ok:
line_edit.setFocus()
line_edit.selectAll()
return path_ok
def validate_settings(self):
return True
def apply_settings(self):
return True