Module soitool.modules.module_code_phrase
SOI module for coded phrases.
Expand source code
"""SOI module for coded phrases."""
import secrets
from PySide2.QtWidgets import (
QTableWidgetItem,
QSizePolicy,
QWidget,
QVBoxLayout,
)
from PySide2.QtCore import Qt
from PySide2.QtGui import QIcon
from soitool.modules.module_base import (
ModuleBase,
DEFAULT_FONT,
prepare_table_for_pdf_export,
prepare_line_edit_for_pdf_export,
is_event_add_row,
is_event_remove_row,
)
from soitool.modules.fit_to_contents_widgets import (
TableWithSizeOfContent,
LineEditWithSizeOfContent,
)
START_ROWS = 1
CODE_COLUMN = 0
PHRASE_COLUMN = 1
class NoMoreAvailableWords(Exception):
"""There are no more available words."""
class NoMoreAvailableCategories(Exception):
"""There are no more available categories."""
class Meta(type(ModuleBase), type(QWidget)):
"""Used as a metaclass to enable multiple inheritance."""
class CodePhraseModule(ModuleBase, QWidget, metaclass=Meta):
"""Module for coded phrases.
Codes are words fetched from the database in a certain category. All
instances of this module is guaranteed to use different categories, and
thereby different words for their codes.
## Note about `self.adjustSize`
When used outside a layout the widget may not be asked to resize itself.
For this reason we need to call `self.adjustSize` manually when we want the
size to update.
Parameters
----------
database : soitool.database.Database
Database to fetch categoried words from.
data : list
Data to initialize module from. See `self.initialize_from_data` for
format.
category : str
Category to fetch words from to use as codes. If not passed a unique
category will be chosen at random.
Raises
------
NoMoreAvailableCategories
Indicates that there are no more categories to choose from.
"""
used_categories = []
"""List of categories that are used by other instances of this class."""
def __init__(self, database, data=None, category=None):
self.type = CodePhraseModule.__name__
QWidget.__init__(self)
ModuleBase.__init__(self)
self.line_edit_header = LineEditWithSizeOfContent("KODEFRASER")
self.line_edit_header.setFont(self.headline_font)
self.line_edit_header.setAlignment(Qt.AlignCenter)
self.table = TableWithSizeOfContent(0, 2)
self.table.setFont(DEFAULT_FONT)
self.table.setStyleSheet(
"QTableView { gridline-color: black; }"
"QHeaderView::section { border: 1px solid black }"
)
# Qt should let this widget be exactly the size of it's sizeHint
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
# See class docstring
self.line_edit_header.textChanged.connect(self.adjustSize)
self.table.cellChanged.connect(self.adjustSize)
self.database = database
self.table.setHorizontalHeaderItem(0, QTableWidgetItem("Kode"))
self.table.setHorizontalHeaderItem(1, QTableWidgetItem("Frase"))
self.table.horizontalHeader().setStyleSheet("font-weight: bold")
# Forcing height of the header because it changes from screen to screen
self.table.horizontalHeader().setFixedHeight(30)
# To ensure table is initially larger than title
self.table.horizontalHeader().setMinimumSectionSize(125)
self.table.verticalHeader().hide()
# Initialize either default values or from data. Either way from this
# point on the widget should never have less than one row
if data is None:
self.initialize_default(category)
else:
self.initialize_from_data(data)
self.layout = QVBoxLayout()
self.layout.setSpacing(0)
self.layout.setMargin(0)
self.layout.addWidget(self.line_edit_header)
self.layout.addWidget(self.table)
self.setLayout(self.layout)
def initialize_from_data(self, data):
"""Initialize from data.
Parameters
----------
data : list
Refer to `get_data` for detailed description.
"""
self.line_edit_header.setText(data[0])
self.category = data[1]
self.used_categories.append(self.category)
self.available_words = data[2]
table = data[3]
for i, row in enumerate(table):
self.table.insertRow(i)
self.set_code_table_item(i, row[CODE_COLUMN])
self.set_phrase_table_item(i, row[PHRASE_COLUMN])
def initialize_default(self, category=None):
"""Initialize to default values.
Prepares `self.category` and `self.available_words` from
`self.database`.
Parameters
----------
category : str
Category to use. If 'None' will choose one from the database.
Raises
------
NoMoreAvailableCategories
Indicates that there are no more categories to choose from.
"""
if category is not None:
self.category = category
else:
self.category = self.get_category()
self.available_words = self.database.get_category_words(self.category)
for _ in range(START_ROWS):
self.add_row()
def add_row(self):
"""Add row below selected row.
New row will include non-editable code.
"""
code = None
try:
code = self.get_code()
except NoMoreAvailableWords:
# NOTE: Failing silently here, but could notify user if we wanted
# to
pass
else:
self.table.insertRow(self.table.currentRow() + 1)
self.set_code_table_item(self.table.currentRow() + 1, code)
def set_code_table_item(self, row, code):
"""Set code at row.
Parameters
----------
row : int
Row to set code.
code : str
Code to set.
"""
item = QTableWidgetItem(code)
item.setFlags(item.flags() ^ Qt.ItemIsEditable)
self.table.setItem(row, CODE_COLUMN, item)
def set_phrase_table_item(self, row, phrase):
"""Set phrase at row.
Parameters
----------
row : int
Row to set code.
phrase : str
Phrase to set.
"""
item = QTableWidgetItem(phrase)
self.table.setItem(row, PHRASE_COLUMN, item)
def remove_row(self):
"""Remove selected row.
Also adds code to list of available codes for re-use.
"""
if self.table.rowCount() > 1:
code = self.table.item(self.table.currentRow(), CODE_COLUMN).text()
self.table.removeRow(self.table.currentRow())
self.available_words.append(code)
self.adjustSize()
def keyPressEvent(self, event):
"""Launch actions when specific combinations of keys are pressed.
* CTRL +: New row under current.
* CTRL -: Remove current row.
Parameters
----------
event : QKeyEvent
Event sent by Qt for us to handle.
"""
if is_event_add_row(event):
self.add_row()
elif is_event_remove_row(event):
self.remove_row()
def get_category(self):
"""Get available category.
Queries database for all categories and picks one at random that has
not been used by an instance of this class before. This function
utilizes the class variable `self.used_categories`.
Returns
-------
str
Available category.
Raises
------
NoMoreAvailableCategories
Indicates that there are no more categories to choose from.
"""
all_categories = self.database.get_categories()
available_categories = [
category
for category in all_categories
if category not in self.used_categories
]
if not available_categories:
raise NoMoreAvailableCategories()
category = secrets.choice(available_categories)
self.used_categories.append(category)
return category
def get_code(self):
"""Get available code.
Returns
-------
str
Available code.
Raises
------
NoMoreAvailableWords
Indicate that there are no more words to use as codes.
"""
if not self.available_words:
raise NoMoreAvailableWords()
code = secrets.choice(self.available_words)
self.available_words.remove(code)
return code
def get_size(self):
"""Get size of widget.
Returns
-------
Tuple
(width, height)
"""
size = self.sizeHint()
return (size.width(), size.height())
def get_data(self):
"""Return list containing module data.
Returns
-------
list
First element in the list is the header. Second element is the
category. Third element is available words in the category. Fourth
element is a list of rows, each containing a list of columns.
"""
content = []
content.append(self.line_edit_header.text())
content.append(self.category)
content.append(self.available_words)
table = []
for i in range(self.table.rowCount()):
row = []
for j in range(self.table.columnCount()):
item = self.table.item(i, j)
if item is not None:
row.append(item.text())
else:
row.append("")
table.append(row)
content.append(table)
return content
def prepare_for_pdf_export(self):
"""Prepare for PDF-export."""
prepare_line_edit_for_pdf_export(self.line_edit_header)
prepare_table_for_pdf_export(self.table)
@staticmethod
def get_user_friendly_name():
"""Get user-friendly name of module."""
return "Kodefraser"
@staticmethod
def get_icon():
"""Get icon of module."""
return QIcon("soitool/media/codephrasemodule.png")
Classes
class CodePhraseModule (database, data=None, category=None)
-
Module for coded phrases.
Codes are words fetched from the database in a certain category. All instances of this module is guaranteed to use different categories, and thereby different words for their codes.
Note about
self.adjustSize
When used outside a layout the widget may not be asked to resize itself. For this reason we need to call
self.adjustSize
manually when we want the size to update.Parameters
database
:Database
- Database to fetch categoried words from.
data
:list
- Data to initialize module from. See
self.initialize_from_data
for format. category
:str
- Category to fetch words from to use as codes. If not passed a unique category will be chosen at random.
Raises
NoMoreAvailableCategories
- Indicates that there are no more categories to choose from.
Class-variable 'type' should be set by derived class.
Expand source code
class CodePhraseModule(ModuleBase, QWidget, metaclass=Meta): """Module for coded phrases. Codes are words fetched from the database in a certain category. All instances of this module is guaranteed to use different categories, and thereby different words for their codes. ## Note about `self.adjustSize` When used outside a layout the widget may not be asked to resize itself. For this reason we need to call `self.adjustSize` manually when we want the size to update. Parameters ---------- database : soitool.database.Database Database to fetch categoried words from. data : list Data to initialize module from. See `self.initialize_from_data` for format. category : str Category to fetch words from to use as codes. If not passed a unique category will be chosen at random. Raises ------ NoMoreAvailableCategories Indicates that there are no more categories to choose from. """ used_categories = [] """List of categories that are used by other instances of this class.""" def __init__(self, database, data=None, category=None): self.type = CodePhraseModule.__name__ QWidget.__init__(self) ModuleBase.__init__(self) self.line_edit_header = LineEditWithSizeOfContent("KODEFRASER") self.line_edit_header.setFont(self.headline_font) self.line_edit_header.setAlignment(Qt.AlignCenter) self.table = TableWithSizeOfContent(0, 2) self.table.setFont(DEFAULT_FONT) self.table.setStyleSheet( "QTableView { gridline-color: black; }" "QHeaderView::section { border: 1px solid black }" ) # Qt should let this widget be exactly the size of it's sizeHint self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) # See class docstring self.line_edit_header.textChanged.connect(self.adjustSize) self.table.cellChanged.connect(self.adjustSize) self.database = database self.table.setHorizontalHeaderItem(0, QTableWidgetItem("Kode")) self.table.setHorizontalHeaderItem(1, QTableWidgetItem("Frase")) self.table.horizontalHeader().setStyleSheet("font-weight: bold") # Forcing height of the header because it changes from screen to screen self.table.horizontalHeader().setFixedHeight(30) # To ensure table is initially larger than title self.table.horizontalHeader().setMinimumSectionSize(125) self.table.verticalHeader().hide() # Initialize either default values or from data. Either way from this # point on the widget should never have less than one row if data is None: self.initialize_default(category) else: self.initialize_from_data(data) self.layout = QVBoxLayout() self.layout.setSpacing(0) self.layout.setMargin(0) self.layout.addWidget(self.line_edit_header) self.layout.addWidget(self.table) self.setLayout(self.layout) def initialize_from_data(self, data): """Initialize from data. Parameters ---------- data : list Refer to `get_data` for detailed description. """ self.line_edit_header.setText(data[0]) self.category = data[1] self.used_categories.append(self.category) self.available_words = data[2] table = data[3] for i, row in enumerate(table): self.table.insertRow(i) self.set_code_table_item(i, row[CODE_COLUMN]) self.set_phrase_table_item(i, row[PHRASE_COLUMN]) def initialize_default(self, category=None): """Initialize to default values. Prepares `self.category` and `self.available_words` from `self.database`. Parameters ---------- category : str Category to use. If 'None' will choose one from the database. Raises ------ NoMoreAvailableCategories Indicates that there are no more categories to choose from. """ if category is not None: self.category = category else: self.category = self.get_category() self.available_words = self.database.get_category_words(self.category) for _ in range(START_ROWS): self.add_row() def add_row(self): """Add row below selected row. New row will include non-editable code. """ code = None try: code = self.get_code() except NoMoreAvailableWords: # NOTE: Failing silently here, but could notify user if we wanted # to pass else: self.table.insertRow(self.table.currentRow() + 1) self.set_code_table_item(self.table.currentRow() + 1, code) def set_code_table_item(self, row, code): """Set code at row. Parameters ---------- row : int Row to set code. code : str Code to set. """ item = QTableWidgetItem(code) item.setFlags(item.flags() ^ Qt.ItemIsEditable) self.table.setItem(row, CODE_COLUMN, item) def set_phrase_table_item(self, row, phrase): """Set phrase at row. Parameters ---------- row : int Row to set code. phrase : str Phrase to set. """ item = QTableWidgetItem(phrase) self.table.setItem(row, PHRASE_COLUMN, item) def remove_row(self): """Remove selected row. Also adds code to list of available codes for re-use. """ if self.table.rowCount() > 1: code = self.table.item(self.table.currentRow(), CODE_COLUMN).text() self.table.removeRow(self.table.currentRow()) self.available_words.append(code) self.adjustSize() def keyPressEvent(self, event): """Launch actions when specific combinations of keys are pressed. * CTRL +: New row under current. * CTRL -: Remove current row. Parameters ---------- event : QKeyEvent Event sent by Qt for us to handle. """ if is_event_add_row(event): self.add_row() elif is_event_remove_row(event): self.remove_row() def get_category(self): """Get available category. Queries database for all categories and picks one at random that has not been used by an instance of this class before. This function utilizes the class variable `self.used_categories`. Returns ------- str Available category. Raises ------ NoMoreAvailableCategories Indicates that there are no more categories to choose from. """ all_categories = self.database.get_categories() available_categories = [ category for category in all_categories if category not in self.used_categories ] if not available_categories: raise NoMoreAvailableCategories() category = secrets.choice(available_categories) self.used_categories.append(category) return category def get_code(self): """Get available code. Returns ------- str Available code. Raises ------ NoMoreAvailableWords Indicate that there are no more words to use as codes. """ if not self.available_words: raise NoMoreAvailableWords() code = secrets.choice(self.available_words) self.available_words.remove(code) return code def get_size(self): """Get size of widget. Returns ------- Tuple (width, height) """ size = self.sizeHint() return (size.width(), size.height()) def get_data(self): """Return list containing module data. Returns ------- list First element in the list is the header. Second element is the category. Third element is available words in the category. Fourth element is a list of rows, each containing a list of columns. """ content = [] content.append(self.line_edit_header.text()) content.append(self.category) content.append(self.available_words) table = [] for i in range(self.table.rowCount()): row = [] for j in range(self.table.columnCount()): item = self.table.item(i, j) if item is not None: row.append(item.text()) else: row.append("") table.append(row) content.append(table) return content def prepare_for_pdf_export(self): """Prepare for PDF-export.""" prepare_line_edit_for_pdf_export(self.line_edit_header) prepare_table_for_pdf_export(self.table) @staticmethod def get_user_friendly_name(): """Get user-friendly name of module.""" return "Kodefraser" @staticmethod def get_icon(): """Get icon of module.""" return QIcon("soitool/media/codephrasemodule.png")
Ancestors
- ModuleBase
- abc.ABC
- PySide2.QtWidgets.QWidget
- PySide2.QtCore.QObject
- PySide2.QtGui.QPaintDevice
- Shiboken.Object
Class variables
var staticMetaObject
var used_categories
-
List of categories that are used by other instances of this class.
Static methods
def get_icon()
-
Get icon of module.
Expand source code
@staticmethod def get_icon(): """Get icon of module.""" return QIcon("soitool/media/codephrasemodule.png")
def get_user_friendly_name()
-
Get user-friendly name of module.
Expand source code
@staticmethod def get_user_friendly_name(): """Get user-friendly name of module.""" return "Kodefraser"
Methods
def add_row(self)
-
Add row below selected row.
New row will include non-editable code.
Expand source code
def add_row(self): """Add row below selected row. New row will include non-editable code. """ code = None try: code = self.get_code() except NoMoreAvailableWords: # NOTE: Failing silently here, but could notify user if we wanted # to pass else: self.table.insertRow(self.table.currentRow() + 1) self.set_code_table_item(self.table.currentRow() + 1, code)
def get_category(self)
-
Get available category.
Queries database for all categories and picks one at random that has not been used by an instance of this class before. This function utilizes the class variable
self.used_categories
.Returns
str
- Available category.
Raises
NoMoreAvailableCategories
- Indicates that there are no more categories to choose from.
Expand source code
def get_category(self): """Get available category. Queries database for all categories and picks one at random that has not been used by an instance of this class before. This function utilizes the class variable `self.used_categories`. Returns ------- str Available category. Raises ------ NoMoreAvailableCategories Indicates that there are no more categories to choose from. """ all_categories = self.database.get_categories() available_categories = [ category for category in all_categories if category not in self.used_categories ] if not available_categories: raise NoMoreAvailableCategories() category = secrets.choice(available_categories) self.used_categories.append(category) return category
def get_code(self)
-
Get available code.
Returns
str
- Available code.
Raises
NoMoreAvailableWords
- Indicate that there are no more words to use as codes.
Expand source code
def get_code(self): """Get available code. Returns ------- str Available code. Raises ------ NoMoreAvailableWords Indicate that there are no more words to use as codes. """ if not self.available_words: raise NoMoreAvailableWords() code = secrets.choice(self.available_words) self.available_words.remove(code) return code
def get_data(self)
-
Return list containing module data.
Returns
list
- First element in the list is the header. Second element is the category. Third element is available words in the category. Fourth element is a list of rows, each containing a list of columns.
Expand source code
def get_data(self): """Return list containing module data. Returns ------- list First element in the list is the header. Second element is the category. Third element is available words in the category. Fourth element is a list of rows, each containing a list of columns. """ content = [] content.append(self.line_edit_header.text()) content.append(self.category) content.append(self.available_words) table = [] for i in range(self.table.rowCount()): row = [] for j in range(self.table.columnCount()): item = self.table.item(i, j) if item is not None: row.append(item.text()) else: row.append("") table.append(row) content.append(table) return content
def get_size(self)
-
Get size of widget.
Returns
Tuple
- (width, height)
Expand source code
def get_size(self): """Get size of widget. Returns ------- Tuple (width, height) """ size = self.sizeHint() return (size.width(), size.height())
def initialize_default(self, category=None)
-
Initialize to default values.
Prepares
self.category
andself.available_words
fromself.database
.Parameters
category
:str
- Category to use. If 'None' will choose one from the database.
Raises
NoMoreAvailableCategories
- Indicates that there are no more categories to choose from.
Expand source code
def initialize_default(self, category=None): """Initialize to default values. Prepares `self.category` and `self.available_words` from `self.database`. Parameters ---------- category : str Category to use. If 'None' will choose one from the database. Raises ------ NoMoreAvailableCategories Indicates that there are no more categories to choose from. """ if category is not None: self.category = category else: self.category = self.get_category() self.available_words = self.database.get_category_words(self.category) for _ in range(START_ROWS): self.add_row()
def initialize_from_data(self, data)
-
Initialize from data.
Parameters
data
:list
- Refer to
get_data
for detailed description.
Expand source code
def initialize_from_data(self, data): """Initialize from data. Parameters ---------- data : list Refer to `get_data` for detailed description. """ self.line_edit_header.setText(data[0]) self.category = data[1] self.used_categories.append(self.category) self.available_words = data[2] table = data[3] for i, row in enumerate(table): self.table.insertRow(i) self.set_code_table_item(i, row[CODE_COLUMN]) self.set_phrase_table_item(i, row[PHRASE_COLUMN])
def keyPressEvent(self, event)
-
Launch actions when specific combinations of keys are pressed.
- CTRL +: New row under current.
- CTRL -: Remove current row.
Parameters
event
:QKeyEvent
- Event sent by Qt for us to handle.
Expand source code
def keyPressEvent(self, event): """Launch actions when specific combinations of keys are pressed. * CTRL +: New row under current. * CTRL -: Remove current row. Parameters ---------- event : QKeyEvent Event sent by Qt for us to handle. """ if is_event_add_row(event): self.add_row() elif is_event_remove_row(event): self.remove_row()
def prepare_for_pdf_export(self)
-
Prepare for PDF-export.
Expand source code
def prepare_for_pdf_export(self): """Prepare for PDF-export.""" prepare_line_edit_for_pdf_export(self.line_edit_header) prepare_table_for_pdf_export(self.table)
def remove_row(self)
-
Remove selected row.
Also adds code to list of available codes for re-use.
Expand source code
def remove_row(self): """Remove selected row. Also adds code to list of available codes for re-use. """ if self.table.rowCount() > 1: code = self.table.item(self.table.currentRow(), CODE_COLUMN).text() self.table.removeRow(self.table.currentRow()) self.available_words.append(code) self.adjustSize()
def set_code_table_item(self, row, code)
-
Set code at row.
Parameters
row
:int
- Row to set code.
code
:str
- Code to set.
Expand source code
def set_code_table_item(self, row, code): """Set code at row. Parameters ---------- row : int Row to set code. code : str Code to set. """ item = QTableWidgetItem(code) item.setFlags(item.flags() ^ Qt.ItemIsEditable) self.table.setItem(row, CODE_COLUMN, item)
def set_phrase_table_item(self, row, phrase)
-
Set phrase at row.
Parameters
row
:int
- Row to set code.
phrase
:str
- Phrase to set.
Expand source code
def set_phrase_table_item(self, row, phrase): """Set phrase at row. Parameters ---------- row : int Row to set code. phrase : str Phrase to set. """ item = QTableWidgetItem(phrase) self.table.setItem(row, PHRASE_COLUMN, item)
class Meta (name, bases, namespace, **kwargs)
-
Used as a metaclass to enable multiple inheritance.
Expand source code
class Meta(type(ModuleBase), type(QWidget)): """Used as a metaclass to enable multiple inheritance."""
Ancestors
- abc.ABCMeta
- Shiboken.ObjectType
- builtins.type
class NoMoreAvailableCategories (...)
-
There are no more available categories.
Expand source code
class NoMoreAvailableCategories(Exception): """There are no more available categories."""
Ancestors
- builtins.Exception
- builtins.BaseException
class NoMoreAvailableWords (...)
-
There are no more available words.
Expand source code
class NoMoreAvailableWords(Exception): """There are no more available words."""
Ancestors
- builtins.Exception
- builtins.BaseException