class TestQtElements(TestCase): def setUp(self): self.main = QWidget() def test_creation_of_app_buttons(self): app_buttons = qt.AppButtons() self.main.setLayout(app_buttons) self.assertIsInstance(app_buttons.button_new_game, QPushButton) self.assertEqual(app_buttons.button_new_game.text(), 'New Game') self.assertIn(app_buttons.button_new_game, self.main.children()) self.assertIsInstance(app_buttons.button_help, QPushButton) self.assertEqual(app_buttons.button_help.text(), 'Help') self.assertIn(app_buttons.button_help, self.main.children()) self.assertEqual(app_buttons.spacing(), qt.SPACING) def test_creation_of_epidemic_menu(self): menu = qt.EpidemicMenu() self.main.setLayout(menu) self.assertIsInstance(menu.combo_box, QComboBox) self.assertIsInstance(menu.button, QPushButton) self.assertEqual(menu.button.text(), 'Shuffle Epidemic') self.assertIn(menu.combo_box, self.main.children()) self.assertIn(menu.button, self.main.children()) self.assertEqual(menu.spacing(), qt.SPACING) def test_creation_of_destinations_radio_box(self): destinations = { 'dest1': QRadioButton('Destination 1'), 'dest2': QRadioButton('Destination 2'), 'dest3': QRadioButton('Destination 3') } radio_box = qt.DestinationRadioBox(destinations) self.assertEqual(len(radio_box.b_group.buttons()), 3) for item in destinations.values(): self.assertIn(item, radio_box.children()) def test_creation_of_stats_section(self): pass
def get_child_widget(parent: QWidget, name: str) -> Union[Optional[QWidget], None]: for child in parent.children(): if child.objectName() == name: return child sub_child = get_child_widget(child, name) if sub_child is not None: return sub_child return None
def testLoadFileUnicodeFilePath(self): filePath = str(os.path.join(os.path.dirname(__file__), 'test.ui')) loader = QUiLoader() parent = QWidget() w = loader.load(filePath, parent) self.assertNotEqual(w, None) self.assertEqual(len(parent.children()), 1) child = w.findChild(QWidget, "child_object") self.assertNotEqual(child, None) self.assertEqual(w.findChild(QWidget, "grandson_object"), child.findChild(QWidget, "grandson_object"))
def testLoadFile(self): loader = QUiLoader() parent = QWidget() w = loader.load(self._filePath, parent) self.assertNotEqual(w, None) self.assertEqual(len(parent.children()), 1) child = w.findChild(QWidget, "child_object") self.assertNotEqual(child, None) self.assertEqual(w.findChild(QWidget, "grandson_object"), child.findChild(QWidget, "grandson_object"))
def walk_children(root: QtWidgets.QWidget): def key(child): try: k = f"{child.variable_name} - {str(child)}" except: k = str(child) return k if not isinstance(root, QtWidgets.QWidget) and not isinstance( root, QtWidgets.QLayout): return root return { key(child): walk_children(child) for child in root.children() if not isinstance(child, QtWidgets.QLayout) # do not show layouts }
class MainWindow(QMainWindow, Ui_window_main): """Main window of the application.""" def __init__(self, app): """ Initialize the ui files for the application """ super().__init__() self.app = app self.files = FileManager(self.app.config, self.app.data_home) self.player = None self.current_show = None self.setupUi(self) def setupUi(self, window_main): """Setup all the child widgets of the main window""" super().setupUi(window_main) self._setup_tab_browser() self._setup_tab_files() self.action_open_folder.triggered.connect(self.choose_dir) self.action_source_code.triggered.connect(open_source_code) self.action_refresh_library.triggered.connect(self.refresh_library) self.widget_tab.currentChanged.connect(self.handle_current_tab_changed) @Slot(int) def handle_current_tab_changed(self, index): """ Handles when the tab changes in the widget tab. Args: index: The index of the new tab Returns: None """ if index == 2: if not self.media_browser.is_setup: self.media_browser.is_setup = True self.app.pool.submit( self.media_browser.fetch_control_info, self.combobox_genre_tag, self.combobox_streaming, ) self.check_box_adult.setVisible( self.app.config.get('Allow Adult Content', default=False)) def _setup_tab_browser(self): self.media_browser = MediaBrowser( self.app, self.button_sort_order, self.combobox_season, self.spinbox_year_min, self.spinbox_year_max, self.combobox_sort, self.combobox_format, self.combobox_status, self.check_box_on_list, self.check_box_adult) master_layout = QHBoxLayout() self.tab_browser.setLayout(master_layout) left_mid_bot_layout = QHBoxLayout() left_layout = QVBoxLayout() left_layout.setAlignment(Qt.AlignTop) left_layout_control = QWidget() left_layout_control.setLayout(left_layout) left_layout.addWidget(self.label_season) left_layout.addWidget(self.combobox_season) left_layout.addWidget(self.groupbox_year) left_layout.addWidget(self.label_filter) left_mid_bot_layout.addWidget(self.combobox_sort) left_mid_bot_layout.addWidget(self.button_sort_order) left_layout.addLayout(left_mid_bot_layout) left_layout.addWidget(self.combobox_format) left_layout.addWidget(self.combobox_status) left_layout.addWidget(self.combobox_streaming) left_layout.addWidget(self.combobox_genre_tag) left_layout.addWidget(self.check_box_on_list) left_layout.addWidget(self.check_box_adult) left_layout_control.setMaximumWidth(self.groupbox_year.width()) left_layout.setSpacing(0) left_layout.setMargin(0) master_layout.addWidget(left_layout_control) master_layout.addWidget(self.media_browser) def _setup_tab_files(self): """ Sets up the local files tab. Triggered when the tab is selected """ # TODO: Refactor self.files.build_all_from_db() series_layout = FlowLayout() series_layout.setAlignment(Qt.AlignTop) for show in sorted(self.files.series): button = SeriesButton(show, len(self.files.series[show])) button.clicked.connect(self.on_series_click) series_layout.addWidget(button) series_layout.setSizeConstraint(FlowLayout.SetMaximumSize) self.page_widget = QWidget() self.page_widget.setLayout(series_layout) series_page = QScrollArea() series_page.setWidget(self.page_widget) series_page.setWidgetResizable(True) self.stack_local_files.addWidget(series_page) self.stack_local_files.addWidget(QWidget()) def on_series_click(self, *, show=None): """ Sets up the window for the episodes of a show. Triggered when a show is clicked from the local files page """ if show is None: show = self.sender().title self.current_show = show self.stack_local_files.setCurrentIndex(1) menu_layout = QGridLayout() menu = QWidget() layout = FlowLayout() layout.setAlignment(Qt.AlignTop) back_button = QPushButton('Back') back_button.clicked.connect(self.on_back_click) menu_layout.addWidget(back_button, 0, 0) refresh_button = QPushButton('Refresh') refresh_button.clicked.connect(self.refresh_show) menu_layout.addWidget(refresh_button, 1, 0) delete_button = QPushButton('Delete') delete_button.clicked.connect(self.delete_show) menu_layout.addWidget(delete_button, 0, 1) rename_button = QPushButton('Rename') rename_button.clicked.connect(self.rename_show) menu_layout.addWidget(rename_button, 1, 1) label = QLabel(show) menu_layout.addWidget(label, 0, 2, 2, 2) menu.setLayout(menu_layout) menu.setMaximumWidth(self.stack_local_files.width()) menu.setMinimumWidth(self.stack_local_files.width() / 2) layout.addWidget(menu) self.files.series[show].sort() for episode in self.files.series[show]: button = EpisodeButton(episode) button.clicked.connect(self.play_episode) layout.addWidget(button) self.stack_local_files.currentWidget().setLayout(layout) def refresh_show(self): """ Refreshes the episodes for the current show and dumps the new shows to the database. Triggered by clicking Refresh in the view for a show """ new = self.files.refresh_single_show(self.current_show) layout = self.stack_local_files.currentWidget().layout() for episode in new: button = EpisodeButton(episode) button.clicked.connect(self.play_episode) layout.addWidget(button) self.files.dump_to_db() def play_episode(self): """ Plays the selected episode with the users player of choice """ if self.player is not None and self.player.needs_destruction(): self.player = None if self.player is None: self.player = ene.player.get_player(self.app.config) episode = self.sender().path self.player.play(str(episode)) def on_back_click(self): """ Deletes all child widgets of the layout for episodes and the layout itself then returns to the shows page """ self.current_show = None layout = self.stack_local_files.currentWidget().layout() for i in reversed(range(layout.count())): layout.itemAt(i).widget().deleteLater() layout.deleteLater() self.stack_local_files.setCurrentIndex(0) def choose_dir(self) -> Path: """ Choose a directory from a file dialog Returns: The directory path """ args = [self, self.tr("Open Directory"), str(Path.home())] if IS_WIN: args.append(QFileDialog.DontUseNativeDialog) dir_ = QFileDialog.getExistingDirectory(*args) # TODO do something with this return Path(dir_) def refresh_library(self): """ Triggers a full refresh of the users library, updating episode counts and adding new shows to the UI as needed """ old = set(self.files.series.keys()) self.files.refresh_shows() new = set(self.files.series.keys()) - old for show in self.page_widget.children(): if isinstance(show, SeriesButton): show.update_episode_count(len(self.files.series[show.title])) for show in sorted(new): button = SeriesButton(show, len(self.files.series[show])) button.clicked.connect(self.on_series_click) self.page_widget.layout().addWidget(button) self.files.dump_to_db() def rename_show(self): """ Renames the current show. Displays an input box to get the new name """ title = QInputDialog().getText(self, 'Rename', 'New Title:', text=self.current_show) if title[1]: self.files.rename_show(self.current_show, title[0]) def delete_show(self): """ Deletes the current show """ self.files.delete_show(self.current_show) self.on_back_click()
class MainWindow(QMainWindow): forum = Forum(id=-1, name="UNKNOWN") topics = [] forum_model = ForumModel() day_model = DayModel() def __init__(self): QMainWindow.__init__(self) self.date_view = QListView() self.forum_view = QListView() self.contentMax = 0 self.ioloop = asyncio.get_event_loop() icon = QIcon() icon.addPixmap(QPixmap(FAVICON_ICO), QIcon.Normal) self.setWindowIcon(icon) self.setWindowTitle(APP_TITLE) self.settings = QSettings('karoStudio', 'nnm-stats') self.resize(self.settings.value('main/size', QSize(640, 480))) self.move(self.settings.value('main/pos', QPoint(200, 200))) self.splitter = QSplitter() self.get_menu() self.content = QScrollArea() self.content.verticalScrollBar().valueChanged.connect( self.scrollBarChanged) self.content.verticalScrollBar().rangeChanged.connect( self.rangeChanged) self.torrents_list_view = QWidget() layout = QGridLayout() self.torrents_list_view.setLayout(layout) self.content.setWidget(self.torrents_list_view) self.splitter.addWidget(self.content) self.splitter.setSizes([160, 350]) self.setCentralWidget(self.splitter) self.timer = QTimer() self.timer.singleShot(1600, self.load_task) def get_menu(self): scroll = QScrollArea(self) self.forum_view.setStyleSheet("QListView{font: bold 12px;}") self.forum_view.clicked.connect(self.listViewClick) self.forum_view.setModel(self.forum_model) self.date_view.setStyleSheet("QListView{font: bold 12px;}") self.date_view.clicked.connect(self.listViewClick) self.date_view.setModel(self.day_model) menu_splitter = QSplitter(self) menu_splitter.setOrientation(Qt.Vertical) menu_splitter.addWidget(self.forum_view) menu_splitter.addWidget(self.date_view) scroll.setWidget(menu_splitter) scroll.setWidgetResizable(True) self.splitter.addWidget(scroll) def load_task(self): self.ioloop.run_until_complete(self.load_forums()) async def load_forums(self): tasks = [asyncio.ensure_future((get_forums("http://nnmclub.to/")))] done, pending = await asyncio.wait(tasks, return_when=FIRST_COMPLETED) forums = done.pop().result() for forum in forums: print(forum) self.forum_model.add(forum) def load_torrents_task(self, forum, start): self.ioloop.run_until_complete(self.load_torrents(forum, start)) async def load_torrents(self, forum, start=0): tasks = [asyncio.ensure_future((get_topics(forum, start)))] done, pending = await asyncio.wait(tasks, return_when=FIRST_COMPLETED) if start == 0: self.topics = done.pop().result() else: self.topics = self.topics + done.pop().result() layout = QGridLayout() self.topics.sort(key=lambda x: x.likes, reverse=True) days = {} for i, topic in enumerate(self.topics): d = topic.published.date() if d in days.keys(): days[d]['count'] += 1 days[d]['likes'] += topic.likes else: days[d] = {'count': 1, 'likes': topic.likes} # layout.addWidget(TopicView(topic), i, 0) for day in days.keys(): self.day_model.add(day, days[day]) self.torrents_list_view = QWidget() self.torrents_list_view.setLayout(layout) self.content.setWidget(self.torrents_list_view) def rangeChanged(self, vert_min, vert_max): self.contentMax = vert_max def scrollBarChanged(self, value): if value == self.contentMax: print("LOADING {}".format(self.torrents_list_view.children())) self.timer.singleShot( 1000, lambda: self.load_torrents_task(self.forum, len(self.topics))) def listViewClick(self, index): self.forum = self.forum_model.forums[index.row()] self.topics = [] self.setWindowTitle("{} / {}".format(APP_TITLE, self.forum.name)) self.day_model.clear() # self.timer.singleShot(1000, lambda: self.load_torrents_task(self.forum, 0)) self.timer.timeout.connect( lambda: self.load_torrents_task(self.forum, len(self.topics))) self.timer.start(8000) def closeEvent(self, event): self.settings.setValue('main/size', self.size()) self.settings.setValue('main/pos', self.pos()) self.ioloop.close()