diff --git a/app/controller/controller.py b/app/controller/controller.py index dc32b6d..ec8e4ff 100644 --- a/app/controller/controller.py +++ b/app/controller/controller.py @@ -2,12 +2,14 @@ from PyQt5.QtWidgets import QMessageBox from enum import Enum, auto + class Mode(Enum): BROWSING = 0 SEARCHING = auto() PROCESSING = auto() PRUNING = auto() + class Controller: def __init__(self, model, view): self.state = Mode.BROWSING @@ -20,7 +22,7 @@ def __init__(self, model, view): def set_state(self, state): self.state = state - + @property def view_elem(self): return self.view.active_elements @@ -84,22 +86,24 @@ def connect_sigs(self): def on_article_discovered(self, article_json, progress): article_data = self.model.create_article_data(article_json) - self.view.display_article(self.search_page, article_data, progress) + self.view.display_article( + self.search_page, 'search', article_data, progress) def on_article_processed(self, article, ids_list, progress): article_data = self.model.update_article(article, ids_list) - self.view.display_article(self.parsed_page, article_data, progress) + self.view.display_article( + self.parsed_page, 'parsed', article_data, progress) def search_for_articles(self): self.set_state(Mode.SEARCHING) self.model.reset_for_searching() self.view.tab_widget.setCurrentIndex(0) - + if self.model.search_thread.isRunning(): QMessageBox.warning( self.view, "Search in Progress", - "A search is already in progress. " + "A search is already in progress. " "Please wait or stop the current search.") return @@ -107,7 +111,7 @@ def search_for_articles(self): query = self.search_page.query_field.text() if not query: return - + # TODO these are low level concerns that should be handled by the view self.search_page.article_list.clear() self.search_page.previews.hide() @@ -116,24 +120,24 @@ def search_for_articles(self): self.search_page.search_status.setText("Searching...") self.search_page.stop_search_btn.show() self.search_page.stop_search_btn.setEnabled(True) - + self.model.search_thread.query = query self.model.search_thread.start() def stop_search(self, search_thread): search_thread.quit() - + # TODO these are low level concerns that should be handled by the view self.search_page.search_status.setText("Stopping search...") - self.search_page.prog_bar.hide() + self.search_page.prog_bar.hide() self.search_page.search_status.setText("Search stopped.") self.search_page.stop_search_btn.hide() self.search_page.stop_search_btn.setEnabled(False) - + self.set_state(Mode.BROWSING) - + def send_search_stop(self): - if self.model.search_thread.isRunning(): + if self.model.search_thread.isRunning(): self.model.search_thread.stop() self.model.search_thread.wait() @@ -159,8 +163,9 @@ def handle_article_click(self, item): self.view.update_article_display( article, 'supp_files', - self.view.suppfilelistitem_factory) - + self.view.suppfilelistitem_factory, + 'search') + # TODO do we need to pass in the factory if the relevant class can be # derived from list item type within view.update_article_display? # ... could just do away with the factories altogether @@ -178,7 +183,8 @@ def handle_processed_article_click(self, item): self.view.update_article_display( article, 'processed_tables', - self.view.processedtablelistitem_factory) + self.view.processedtablelistitem_factory, + 'parsed') for i in range(self.view_elem.supp_files_view.count()): list_item = self.view_elem.supp_files_view.item(i) @@ -189,18 +195,19 @@ def handle_pruned_article_click(self, item): self.view_elem.previews.hide() article_id = item.data(Qt.UserRole) article = self.model.bibliography.get_article(article_id) - + self.view.update_article_display( article, 'pruned_article_tables', - self.view.processedtablelistitem_factory) + self.view.processedtablelistitem_factory, + 'pruned') for i in range(self.view_elem.supp_files_view.count()): list_item = self.view_elem.supp_files_view.item(i) widget = self.view_elem.supp_files_view.itemWidget(list_item) widget.preview_requested.connect(self.preview_pruned_table) - def load_preview(self, data, table_id=None, callback=None): + def load_preview(self, data, table_id=None, callback=None, context=None): use_checkable_header = self.view_elem \ .__class__.__name__ == 'ProcessedPageElements' @@ -212,8 +219,13 @@ def load_preview(self, data, table_id=None, callback=None): processed_table = self.model.processed_table_manager \ .get_processed_table(table_id) if table_id else None - checked_columns = processed_table \ - .checked_columns if processed_table else None + checked_columns = None + if context == 'parsed': + checked_columns = processed_table \ + .checked_columns if processed_table else None + elif context == 'pruned': + checked_columns = processed_table \ + .pruned_columns if processed_table else None self.view.display_multisheet_table( data, use_checkable_header, table_id, callback, checked_columns) @@ -223,11 +235,11 @@ def load_preview(self, data, table_id=None, callback=None): def preview_supp_file(self, file_id): file_data = self.model.file_manager.get_file(file_id) self.view.start_load_animation() - + if self.model.preview_thread.isRunning(): self.model.preview_thread.quit() self.model.preview_thread.wait() - + self.model.preview_thread.file_url = file_data.url self.model.preview_thread.start() @@ -235,17 +247,19 @@ def preview_processed_table(self, table_id): table_data = { "sheet": self.model.table_db_manager.get_processed_table_data( table_id)} - + self.view.start_load_animation() - self.load_preview(table_data, table_id, self.update_checked_columns) + self.load_preview(table_data, table_id, + self.update_checked_columns, 'parsed') def preview_pruned_table(self, table_id): table_data = { "sheet": self.model.table_db_manager.get_post_pruning_table_data( table_id)} - + self.view.start_load_animation() - self.load_preview(table_data, table_id, self.update_checked_columns) + self.load_preview(table_data, table_id, + self.update_pruned_columns, 'pruned') def update_checked_columns(self, table_id, checked_columns): processed_table = self.model.processed_table_manager \ @@ -254,16 +268,30 @@ def update_checked_columns(self, table_id, checked_columns): if processed_table: processed_table.checked_columns = checked_columns + def update_pruned_columns(self, table_id, checked_columns): + processed_table = self.model.processed_table_manager \ + .get_processed_table(table_id) + + if processed_table: + processed_table.pruned_columns = checked_columns + def prune_tables_and_columns(self): + # get current page from view_elem (class name doesnt work!) + current_page = self.view.tab_widget.currentIndex() + if current_page == 1: + context = 'parsed' + elif current_page == 2: + context = 'pruned' + self.view.tab_widget.setCurrentIndex(2) - self.model.prune_tables_and_columns() + self.model.prune_tables_and_columns(context) + self.view.clear_article_list_and_files_view() - + # display all selected articles in the pruned page - # TODO need to make it so articles w/ no tables dont appear here - for article in self.model.bibliography.get_selected_articles(): - self.view.display_article(self.pruned_page, article, 0) - + for article in self.model.bibliography.get_selected_articles(context): + self.view.display_article(self.pruned_page, 'pruned', article, 0) + def filter_tables(self): query = self.view_elem.query_filter_field.text() if not query: @@ -279,15 +307,15 @@ def on_proceed(self): "Do you want to stop the current search and proceed?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) - + if reply == QMessageBox.Yes: self.send_search_stop() else: - return + return self.model.reset_for_processing() self.view.tab_widget.setCurrentIndex(1) - + if self.model.processing_thread.isRunning(): QMessageBox.warning( self.view, @@ -297,13 +325,17 @@ def on_proceed(self): self.set_state(Mode.PROCESSING) self.model.processing_thread.should_stop = False - + # TODO these are low level concerns that should be handled by the view self.view_elem.article_list.clear() self.view_elem.previews.hide() self.view_elem.prog_bar.setValue(0) self.view_elem.prog_bar.show() - - selected_articles = self.model.bibliography.get_selected_articles() + + for article in self.model.bibliography.articles.values(): + article.cascade_checked_state('search') + + selected_articles = self.model.bibliography.get_selected_articles( + 'search') self.model.processing_thread.selected_articles = selected_articles self.model.processing_thread.start() diff --git a/app/model/article_managers.py b/app/model/article_managers.py index 32ffcc4..948dd7c 100644 --- a/app/model/article_managers.py +++ b/app/model/article_managers.py @@ -1,50 +1,67 @@ +from dataclasses import dataclass +from typing import Any + class BaseData: def alert_observers(self): return False + def checkbox_togglable(self): + return True class SuppFile(BaseData): - def __init__(self, article_id, url, id): + def __init__(self, article, url, id): self.checked = True - self.article_id = article_id + self.article = article + self.article_id = article.pmc_id self.url = url self.id = id + + def checkbox_toggled(self): + self.article.update_based_on_elements('search') class ProcessedTable(BaseData): - def __init__(self, article_id, id, file_id, num_columns=None): + def __init__(self, article, id, file_id, num_columns=None): self.checked = True - self.article_id = article_id + self.article = article + self.article_id = article.pmc_id self.id = id self.file_id = file_id + self.pruned_columns = [] if num_columns is not None: self.checked_columns = list(range(num_columns)) else: self.checked_columns = [] - self.observers = [] + self.observers = {} - def alert_observers(self): - return True - - def register_observer(self, observer): - self.observers.append(observer) - - def remove_observer(self, observer): - self.observers.remove(observer) + def register_observer(self, observer, context): + self.observers[context] = observer + + def remove_observer(self, context): + del self.observers[context] def notify_observers(self): - for observer in self.observers: + for observer in self.observers.values(): observer.update(self) + + def alert_observers(self): + return True - def checkbox_toggled(self): + def checkbox_toggled(self, context): self.notify_observers() - - def set_checked(self, state): + if context == 'pruned': + return + + self.article.update_based_on_elements(context) + + def set_checked(self, state, context): was_checked = self.checked self.checked = state if state != was_checked: + self.article.update_based_on_elements(context) self.notify_observers() + class SuppFileManager: def __init__(self): self.supp_files = {} @@ -54,7 +71,7 @@ def add_file(self, file): def get_file(self, file_id): return self.supp_files.get(file_id) - + def reset(self): self.supp_files = {} @@ -68,24 +85,74 @@ def add_processed_table(self, table): def get_processed_table(self, table_id): return self.processed_tables.get(table_id) - + def reset(self): self.processed_tables = {} +@dataclass +class CheckedForContext: + search: bool = True + parsed: bool = True + pruned: bool = True + parent: Any = None + + class Article(BaseData): def __init__( self, title, abstract, pmc_id, supp_files=[], processed_tables=[]): - self.checked = True + self.checked = CheckedForContext(parent=self) self.title = title self.abstract = abstract self.pmc_id = pmc_id self.supp_files = supp_files self.processed_tables = processed_tables + self.pruned_tables = [] + self.observers = {} + + def cascade_checked_state(self, context, is_checked=None): + if is_checked is None: + is_checked = getattr(self.checked, context) + + hierarchy = ["search", "parsed", "pruned"] + + # Find the position of the current context in the hierarchy + index = hierarchy.index(context) + + # Update the checked state of the parent context (preserves initial + # state for the initially called context) + setattr(self.checked, context, is_checked) + + # Stop recursion if we're at the last element in the hierarchy + if index == len(hierarchy) - 1: + return + + for sub_context in hierarchy[index + 1:]: + self.cascade_checked_state(sub_context, is_checked) + + def alert_observers(self): + return True + + def register_observer(self, observer, context): + self.observers[context] = observer + + def notify_observers(self, context): + self.observers[context].update(self) + + def update_based_on_elements(self, context): + has_checked = self.has_checked_elements(context) + setattr(self.checked, context, has_checked) + self.notify_observers(context) + + def has_checked_elements(self, context): + if context == 'search': + return any(f.checked for f in self.supp_files) + elif context == 'parsed': + return any(t.checked for t in self.processed_tables) def get_file(self, file_id): return next((f for f in self.supp_files if f.id == file_id), None) - + def get_table_by_id(self, table_id): return next((t for t in self.processed_tables if t.id == table_id), None) @@ -100,8 +167,13 @@ def add_article(self, article): def get_article(self, article_id): return self.articles.get(article_id) - def get_selected_articles(self): - return [p for p in self.articles.values() if p.checked] - + def get_selected_articles(self, context): + selected = [] + for article in self.articles.values(): + if getattr(article.checked, context): + selected.append(article) + + return selected + def reset(self): - self.articles = {} \ No newline at end of file + self.articles = {} diff --git a/app/model/database.py b/app/model/database.py index ecb20e5..12ba95b 100644 --- a/app/model/database.py +++ b/app/model/database.py @@ -6,20 +6,23 @@ Base = declarative_base() + class ProcessedTableDBEntry(Base): __tablename__ = 'processed_tables' - + table_id = Column(String, primary_key=True) original_file_id = Column(String) table_data = Column(BLOB) + class PostPruningTableDBEntry(Base): __tablename__ = 'post_pruning_tables' - + table_id = Column(String, primary_key=True) original_file_id = Column(String) table_data = Column(BLOB) + class TableDBManager: def __init__(self, db_url=f"sqlite:///tables-{str(uuid4())}.db"): self.engine = create_engine(db_url) @@ -29,43 +32,47 @@ def __init__(self, db_url=f"sqlite:///tables-{str(uuid4())}.db"): def save_table(self, table_class, table_id, original_file_id, table_data): with self.Session() as session: new_table = table_class( - table_id=table_id, - original_file_id=str(original_file_id), - table_data=table_data + table_id=table_id, + original_file_id=str(original_file_id), + table_data=table_data ) session.add(new_table) session.commit() - + def update_table(self, table_class, table_id, table_data): with self.Session() as session: - existing_table = session.query(table_class).filter_by(table_id=table_id).first() + existing_table = session.query( + table_class).filter_by(table_id=table_id).first() if existing_table: existing_table.table_data = table_data session.commit() def get_processed_table_data(self, table_id): return self.get_table_data(ProcessedTableDBEntry, table_id) - + def get_post_pruning_table_data(self, table_id): return self.get_table_data(PostPruningTableDBEntry, table_id) def get_table_data(self, table_class, table_id): with self.Session() as session: - table = session.query(table_class).filter_by(table_id=table_id).first() + table = session.query(table_class).filter_by( + table_id=table_id).first() if table: table_data = pickle.loads(table.table_data) table_data.reset_index(drop=True, inplace=True) return table_data return None - + def get_table_object(self, table_class, table_id): with self.Session() as session: - table = session.query(table_class).filter_by(table_id=table_id).first() + table = session.query(table_class).filter_by( + table_id=table_id).first() return table - def delete_table(self, table_class, table_id): + def delete_table(self, table_class, table_id): with self.Session() as session: - table = session.query(table_class).filter_by(table_id=table_id).first() + table = session.query(table_class).filter_by( + table_id=table_id).first() if table: session.delete(table) session.commit() @@ -75,7 +82,9 @@ def reset(self): session.query(ProcessedTableDBEntry).delete() session.query(PostPruningTableDBEntry).delete() session.commit() - + + def processed_df_to_db(db_manager, table_id, original_file_id, df): serialized_df = pickle.dumps(df) - db_manager.save_table(ProcessedTableDBEntry, table_id, original_file_id, serialized_df) \ No newline at end of file + db_manager.save_table(ProcessedTableDBEntry, table_id, + original_file_id, serialized_df) diff --git a/app/model/model.py b/app/model/model.py index 9ae1d63..5af29ed 100644 --- a/app/model/model.py +++ b/app/model/model.py @@ -6,6 +6,7 @@ from model.threading import SearchThread, FilePreviewThread, FileProcessingThread import scripts.query_parser as qp + class Model: def __init__(self): self.article_list_filtered = {} @@ -13,7 +14,7 @@ def __init__(self): self.bibliography = Bibliography() self.file_manager = SuppFileManager() self.search_thread = SearchThread() - self.preview_thread = FilePreviewThread("") + self.preview_thread = FilePreviewThread("") self.table_db_manager = TableDBManager() self.processed_table_manager = ProcessedTableManager() self.processing_thread = FileProcessingThread(self.table_db_manager) @@ -21,10 +22,10 @@ def __init__(self): def update_supp_files(self, article, article_json): supp_files = [] for file_url in article_json["SupplementaryFiles"]: - supp_file = SuppFile(article.pmc_id, file_url, uuid4()) + supp_file = SuppFile(article, file_url, uuid4()) self.file_manager.add_file(supp_file) supp_files.append(supp_file) - + article.supp_files = supp_files def create_article_data(self, article_json): @@ -43,17 +44,17 @@ def update_processed_tables(self, article, ids_list): for table_id, file_id in ids_list: table_data = self.table_db_manager.get_processed_table_data( table_id) - + if table_data is not None: num_columns = len(table_data.columns) else: num_columns = None processed_table = ProcessedTable( - article.pmc_id, + article, table_id, file_id, num_columns) - + self.processed_table_manager.add_processed_table(processed_table) processed_tables.append(processed_table) return processed_tables @@ -68,20 +69,15 @@ def add_processed_tables(self, file_id, tables): file = self.file_manager.get_file(file_id) file.processed_tables = tables - def prune_tables_and_columns(self): - for article in self.bibliography.get_selected_articles(): - ids = [table.id - for table - in self.article_list_filtered.get(article.pmc_id, []) - if table.checked] - - # kind of an ugly way to do this + def prune_tables_and_columns(self, context): + for article in self.bibliography.get_selected_articles(context): + + # TODO unspaghettify this tables_to_prune = [table for table in article.processed_tables if table.checked] for table in tables_to_prune: - print(f"Pruning table {table.id}") serialized_df = None data = self.table_db_manager.get_processed_table_data(table.id) @@ -93,7 +89,7 @@ def prune_tables_and_columns(self): existing_table = self.table_db_manager.get_table_object( PostPruningTableDBEntry, table.id) - + if existing_table: self.table_db_manager.update_table( PostPruningTableDBEntry, @@ -106,30 +102,26 @@ def prune_tables_and_columns(self): table.file_id, serialized_df) - - kept_tables = [self.processed_table_manager.get_processed_table(id) - for id in ids - if - self.processed_table_manager.get_processed_table(id) - is not None] - - for table in kept_tables: - if table.checked_columns is not None: - latest_data = self.table_db_manager \ - .get_processed_table_data(table.id) - - if latest_data is not None: - table.checked_columns = list(range(len( - latest_data.columns))) - + article.pruned_tables = tables_to_prune + + for table in tables_to_prune: + latest_data = self.table_db_manager \ + .get_post_pruning_table_data(table.id) + + if latest_data is not None: + table.pruned_columns = list(range(len( + latest_data.columns))) + def filter_tables(self, query): - for article in self.bibliography.get_selected_articles(): + for article in self.bibliography.get_selected_articles('parsed'): for processed_table in article.processed_tables: table_data = self.table_db_manager.get_processed_table_data( processed_table.id) - + processed_table.set_checked(bool(qp.search( - query, [(processed_table.id, table_data.to_string())]))) + query, + [(processed_table.id, table_data.to_string())])), + 'parsed') def reset_for_searching(self): self.bibliography.reset() @@ -138,4 +130,4 @@ def reset_for_searching(self): def reset_for_processing(self): self.article_list_filtered = {} self.processed_table_manager.reset() - self.table_db_manager.reset() \ No newline at end of file + self.table_db_manager.reset() diff --git a/app/views/list.py b/app/views/list.py index 42d7d08..32eac94 100644 --- a/app/views/list.py +++ b/app/views/list.py @@ -2,12 +2,15 @@ from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QCheckBox, QSizePolicy from PyQt5.QtGui import QFontMetrics + class UIListItem(QWidget): - def __init__(self, data, title): + def __init__(self, data, title, skip_checkbox=False): super().__init__() self.data = data self.checkbox = QCheckBox() - self.checkbox.setChecked(self.data.checked) + # hack for now, setChecked wants a boolean which Article.checked is not + if not skip_checkbox: + self.checkbox.setChecked(self.data.checked) self.checkbox.toggled.connect(self.checkbox_toggled) label = QLabel(title) @@ -20,7 +23,7 @@ def __init__(self, data, title): def checkbox_toggled(self): self.data.checked = self.checkbox.isChecked() - if self.data.alert_observers(): + if self.data.checkbox_togglable: self.data.checkbox_toggled() def mousePressEvent(self, event): @@ -29,10 +32,30 @@ def mousePressEvent(self, event): list_item = list_widget.itemAt(self.parent().mapToParent(event.pos())) list_widget.setCurrentItem(list_item) + class ArticleListItem(UIListItem): - def __init__(self, article_data): - self.article_id = article_data.pmc_id - super().__init__(article_data, article_data.title) + def __init__(self, article, context): + self.article = article + self.article_id = article.pmc_id + self.context = context + self.article.register_observer(self, context) + super().__init__(article, article.title, skip_checkbox=True) + self.load_checked_state() + + def load_checked_state(self): + checked_state = getattr(self.article.checked, self.context) + self.checkbox.setChecked(checked_state) + + def checkbox_toggled(self): + setattr(self.article.checked, self.context, self.checkbox.isChecked()) + self.article.notify_observers(self.context) + + def update(self, article): + new_checked_state = getattr(article.checked, self.context) + self.checkbox.toggled.disconnect(self.checkbox_toggled) + self.checkbox.setChecked(new_checked_state) + self.checkbox.toggled.connect(self.checkbox_toggled) + class DataListItem(UIListItem): preview_requested = pyqtSignal(object) @@ -53,19 +76,37 @@ def get_disp_name(self, text): available_width = self.page.supp_files_view.width() - 150 return font_metrics.elidedText(text, Qt.ElideMiddle, available_width) + class SuppFileListItem(DataListItem): - def __init__(self, main_window, file_data): - super().__init__(main_window, file_data, lambda fd: self.get_disp_name(fd.url.split('/')[-1])) + def __init__(self, main_window, file_data, context): + super().__init__( + main_window, + file_data, + lambda fd: self.get_disp_name(fd.url.split('/')[-1])) + self.context = context # does nothing for now, will always be search self.file_url = file_data.url + def remove(self): + self.data.remove_observer(self) + + class ProcessedTableListItem(DataListItem): - def __init__(self, main_window, file_data): - super().__init__(main_window, file_data, lambda fd: self.get_disp_name(fd.id)) + def __init__(self, main_window, file_data, context): + super().__init__( + main_window, + file_data, + lambda fd: self.get_disp_name(fd.id)) - file_data.register_observer(self) + self.context = context + file_data.register_observer(self, context) + + def checkbox_toggled(self): + self.data.checked = self.checkbox.isChecked() + if self.data.checkbox_togglable: + self.data.checkbox_toggled(self.context) def remove(self): - self.data.remove_observer(self) + self.data.remove_observer(self.context) def update(self, processed_table): # Disconnect the signal before updating the checkbox to avoid triggering the signal again, then reconnect it diff --git a/app/views/view.py b/app/views/view.py index 8058ed5..f2b2b1c 100644 --- a/app/views/view.py +++ b/app/views/view.py @@ -5,29 +5,32 @@ from views.list import ArticleListItem, SuppFileListItem, ProcessedTableListItem from views.page import SearchPageElements, ProcessedPageElements + class View(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) - + self.tab_widget = QTabWidget(self) self.setCentralWidget(self.tab_widget) self.search_page = QWidget(self) self.parsed_page = QWidget(self) self.pruned_page = QWidget(self) - + self.tab_widget.addTab(self.search_page, "Search") self.tab_widget.addTab(self.parsed_page, "Parsing Results") self.tab_widget.addTab(self.pruned_page, "Pruned Results") - + self.search_components = SearchPageElements(self.search_page) self.parsed_components = ProcessedPageElements(self.parsed_page) self.pruned_components = ProcessedPageElements(self.pruned_page) self.init_search_layouts(self.search_components) - self.init_processed_page_layouts(self.parsed_page, self.parsed_components) - self.init_processed_page_layouts(self.pruned_page, self.pruned_components) + self.init_processed_page_layouts( + self.parsed_page, self.parsed_components) + self.init_processed_page_layouts( + self.pruned_page, self.pruned_components) self.init_load_animation() @property @@ -52,7 +55,7 @@ def init_search_layouts(self, components): left_pane.addWidget(components.search_status) left_pane.addWidget(components.article_list) left_pane.addWidget(components.proceed_btn) - + self.init_core_layouts(self.search_page, components, left_pane) def init_processed_page_layouts(self, page, components): @@ -61,12 +64,12 @@ def init_processed_page_layouts(self, page, components): left_pane.addWidget(components.article_list) left_pane.addWidget(QLabel("Filter Query:")) left_pane.addWidget(components.query_filter_field) - left_pane.addWidget(components.filter_btn) + left_pane.addWidget(components.filter_btn) left_pane.addWidget(components.prune_btn) self.init_core_layouts(page, components, left_pane) - - def init_core_layouts(self, page, components, left_pane): + + def init_core_layouts(self, page, components, left_pane): widget_0 = QWidget() widget_0.setLayout(left_pane) @@ -77,7 +80,7 @@ def init_core_layouts(self, page, components, left_pane): mid_pane.addWidget(components.abstract_disp) mid_pane.addWidget(QLabel("Supplementary Files:")) mid_pane.addWidget(components.supp_files_view) - + widget_1 = QWidget() widget_1.setLayout(mid_pane) @@ -106,17 +109,21 @@ def stop_load_animation(self): def update_load_text(self): self.load_dots = (self.load_dots + 1) % 4 - self.active_elements.loading_label.setText("Loading" + "." * self.load_dots) + self.active_elements.loading_label.setText( + "Loading" + "." * self.load_dots) - def suppfilelistitem_factory(self, file_data): - return SuppFileListItem(self, file_data) - - def processedtablelistitem_factory(self, file_data): - return ProcessedTableListItem(self, file_data) + # the context argument is only used as a dummy in this specific function + # because the it's used in the update_article_display function later + # TODO is to harmonise this so we arent passing around dummy arguments + def suppfilelistitem_factory(self, file_data, context): + return SuppFileListItem(self, file_data, context) - def display_article(self, components, article_data, progress): + def processedtablelistitem_factory(self, file_data, context): + return ProcessedTableListItem(self, file_data, context) + + def display_article(self, components, context, article_data, progress): item = QListWidgetItem() - article_widget = ArticleListItem(article_data) + article_widget = ArticleListItem(article_data, context) item.setSizeHint(article_widget.sizeHint()) item.setData(Qt.UserRole, article_data.pmc_id) components.article_list.addItem(item) @@ -124,7 +131,7 @@ def display_article(self, components, article_data, progress): components.prog_bar.setValue(progress + 1) def clear_supp_files_view(self): - # here we get all the list items and deregister their observers + # here we get all the list items and deregister their observers # removing them for i in range(self.active_elements.supp_files_view.count()): item = self.active_elements.supp_files_view.item(i) @@ -132,27 +139,26 @@ def clear_supp_files_view(self): if item: list_item = self.active_elements.supp_files_view.itemWidget( item) - + if list_item and list_item.data.alert_observers(): list_item.remove() - - self.active_elements.supp_files_view.clear() + + self.active_elements.supp_files_view.clear() def clear_article_list_and_files_view(self): - self.active_elements.article_list.clear() - self.clear_supp_files_view() + self.active_elements.article_list.clear() + self.clear_supp_files_view() - def update_article_display(self, article, element_type, list_item_func): + def update_article_display(self, article, element_type, list_item_func, context): self.clear_supp_files_view() self.active_elements.title_disp.setText(article.title) self.active_elements.abstract_disp.setText(article.abstract) - - # This monster must be slain + + # TODO this monster must be slain tables_to_display = [] - if element_type == 'pruned_article_tables': - tables_to_display = [table for table in article.processed_tables - if table.checked] - + if element_type == 'pruned_article_tables': + tables_to_display = article.pruned_tables + elif element_type == 'supp_files': tables_to_display = getattr(article, element_type) else: @@ -160,7 +166,7 @@ def update_article_display(self, article, element_type, list_item_func): for data in tables_to_display: item_container = QListWidgetItem() - file_item = list_item_func(data) + file_item = list_item_func(data, context) file_item.checkbox.setChecked(data.checked) item_container.setSizeHint(file_item.sizeHint()) item_container.setData(Qt.UserRole, data.id) @@ -171,7 +177,8 @@ def update_article_display(self, article, element_type, list_item_func): def display_multisheet_table(self, df_dict, use_checkable_header, table_id=None, callback=None, checked_columns=None): tab_widget = QTabWidget(self.active_elements.previews) for sheet, df in df_dict.items(): - table = self._create_ui_table(df, use_checkable_header, table_id, callback, checked_columns) + table = self._create_ui_table( + df, use_checkable_header, table_id, callback, checked_columns) tab_widget.addTab(table, sheet) self.active_elements.previews_layout.addWidget(tab_widget)