def initUI(self): QToolTip.setFont(QFont('SansSerif', 10)) self.setToolTip('This is a <b>QWidget</b> widget.') #quitBtn = QPushButton('Quit', self) #quitBtn.clicked.connect(self.quitBtnEvent()) #quitBtn.clicked.connect(QCoreApplication.instance().quit) #quitBtn.setToolTip('This is a <b>QPushButton</b> widget.') #quitBtn.resize(quitBtn.sizeHint()) exitAction = QAction(QIcon('application-exit-4.png'), '&Exit', self) exitAction.setShortcut('Alt+F4') exitAction.setStatusTip('Exit Application') exitAction.triggered.connect(qApp.quit) menuBar = QMenuBar() fileMenu = menuBar.addMenu('&File') fileMenu.addAction(exitAction) fileMenu.resize(fileMenu.sizeHint()) toolBar = QToolBar(self) toolBar.addAction(exitAction) #toolBar.resize(toolBar.sizeHint()) toolBar.setFixedHeight(60) hozLine = QFrame() hozLine.setFrameStyle(QFrame.HLine) #hozLine.setSizePolicy(QSizePolicy.Minimum,QSizePolicy.Expanding) statusBar = QStatusBar(self) statusBar.showMessage('Ready') grid = QGridLayout() lbl_1 = QLabel('1,1') lbl_2 = QLabel('1,2') lbl_3 = QLabel('2,1') lbl_4 = QLabel('2,2') grid.addWidget(lbl_1, 1, 1) grid.addWidget(lbl_2, 1, 2) grid.addWidget(lbl_3, 2, 1) grid.addWidget(lbl_4, 2, 2) vbox = QVBoxLayout() vbox.addWidget(menuBar) vbox.addWidget(hozLine) vbox.addWidget(toolBar) # vbox.addWidget(hozLine) vbox.addLayout(grid) vbox.addStretch(1) vbox.addWidget(statusBar) self.setLayout(vbox) self.setGeometry(300, 300, 500, 500) self.setWindowTitle('Photos') self.setWindowIcon(QIcon('camera-photo-5.png')) self.center() self.show()
def __init__(self): super(MainWindow, self).__init__() self.ports = serial.listports() self.port = None # remove close & maximize window buttons #self.setWindowFlags(Qt.CustomizeWindowHint|Qt.WindowMinimizeButtonHint) self.setMinimumSize(850, 450) self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdiArea) self.mdiArea.subWindowActivated.connect(self.updateMenus) self.mdiArea.setViewMode(QMdiArea.TabbedView) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow) self.child = None self.createActions() self.createMenus() self.createStatusBar() self.updateMenus() self.readSettings() self.setWindowTitle("VISCAM") mytoolbar = QToolBar() ports_menu = QComboBox() ports_menu.addItem('Output Port') ports_menu.insertSeparator(1) for port in self.ports: ports_menu.addItem(port) self.ports_menu = ports_menu ports_menu.currentTextChanged.connect(self.setActivePort) mytoolbar.addWidget(ports_menu) mytoolbar.addSeparator() mytoolbar.setMovable(False) mytoolbar.setFixedHeight(60) self.addToolBar(Qt.TopToolBarArea, mytoolbar)
class AppWindow(QMainWindow): "The application's main window" def __init__(self): super().__init__() self.center = QWidget() self.display = QStackedLayout() self.center.setLayout(self.display) # init the manga view variables self.manga_display() # init the chapter view variables self.chapter_display() # init toolbar self.init_toolbar() # init status bar self.init_stat_bar() self.display.addWidget(self.manga_main) self.display.addWidget(self.chapter_main) self.setCentralWidget(self.center) self.setWindowTitle("Happypanda") self.resize(1029, 650) self.show() def init_stat_bar(self): self.status_bar = self.statusBar() self.status_bar.setMaximumHeight(20) self.status_bar.setSizeGripEnabled(False) self.stat_info = QLabel() self.stat_info.setIndent(5) self.sort_main = QAction("Asc", self) sort_menu = QMenu() self.sort_main.setMenu(sort_menu) s_by_title = QAction("Title", sort_menu) s_by_artist = QAction("Artist", sort_menu) sort_menu.addAction(s_by_title) sort_menu.addAction(s_by_artist) self.status_bar.addPermanentWidget(self.stat_info) #self.status_bar.addAction(self.sort_main) self.temp_msg = QLabel() self.temp_timer = QTimer() self.manga_list_view.series_model.ROWCOUNT_CHANGE.connect(self.stat_row_info) self.manga_list_view.series_model.STATUSBAR_MSG.connect(self.stat_temp_msg) def stat_temp_msg(self, msg): self.temp_timer.stop() self.temp_msg.setText(msg) self.status_bar.addWidget(self.temp_msg) self.temp_timer.timeout.connect(self.temp_msg.clear) self.temp_timer.setSingleShot(True) self.temp_timer.start(5000) def stat_row_info(self): r = self.manga_list_view.series_model.rowCount() t = len(self.manga_list_view.series_model._data) self.stat_info.setText("<b>Showing {} of {} </b>".format(r, t)) def manga_display(self): "initiates the manga view" self.manga_main = QWidget() self.manga_main.setContentsMargins(-10, -12, -10, -10) self.manga_view = QHBoxLayout() self.manga_main.setLayout(self.manga_view) manga_delegate = series.CustomDelegate() manga_delegate.BUTTON_CLICKED.connect(self.setCurrentIndex) self.manga_list_view = series.MangaView() self.manga_list_view.setItemDelegate(manga_delegate) self.manga_view.addWidget(self.manga_list_view) def favourite_display(self): "initiates favourite display" pass def chapter_display(self): "Initiates chapter view" self.chapter_main = QWidget() self.chapter_main.setObjectName("chapter_main") # to allow styling this object self.chapter_layout = QHBoxLayout() self.chapter_main.setLayout(self.chapter_layout) #self.chapter_info.setContentsMargins(-8,-7,-7,-7) #self.chapter_info.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.chapter_info_view = series.ChapterInfo() self.chapter_layout.addWidget(self.chapter_info_view) chapter_list_view = series.ChapterView() self.chapter_layout.addWidget(chapter_list_view) #self.chapter.setCollapsible(0, True) #self.chapter.setCollapsible(1, False) def init_toolbar(self): self.toolbar = QToolBar() self.toolbar.setFixedHeight(30) self.toolbar.setWindowTitle("Show") # text for the contextmenu #self.toolbar.setStyleSheet("QToolBar {border:0px}") # make it user defined? self.toolbar.setMovable(False) self.toolbar.setFloatable(False) #self.toolbar.setIconSize(QSize(20,20)) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) spacer_start = QWidget() # aligns the first actions properly spacer_start.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_start) favourite_view_icon = QIcon(gui_constants.STAR_BTN_PATH) favourite_view_action = QAction(favourite_view_icon, "Favourite", self) #favourite_view_action.setText("Manga View") favourite_view_action.triggered.connect(lambda: self.setCurrentIndex(1)) #need lambda to pass extra args self.toolbar.addAction(favourite_view_action) catalog_view_icon = QIcon(gui_constants.HOME_BTN_PATH) catalog_view_action = QAction(catalog_view_icon, "Library", self) #catalog_view_action.setText("Catalog") catalog_view_action.triggered.connect(lambda: self.setCurrentIndex(0)) #need lambda to pass extra args self.toolbar.addAction(catalog_view_action) self.toolbar.addSeparator() series_icon = QIcon(gui_constants.PLUS_PATH) series_action = QAction(series_icon, "Add series...", self) series_action.triggered.connect(self.manga_list_view.SERIES_DIALOG.emit) series_menu = QMenu() series_menu.addSeparator() populate_action = QAction("Populate from folder...", self) populate_action.triggered.connect(self.populate) series_menu.addAction(populate_action) series_action.setMenu(series_menu) self.toolbar.addAction(series_action) spacer_middle = QWidget() # aligns buttons to the right spacer_middle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer_middle) self.search_bar = QLineEdit() self.search_bar.setPlaceholderText("Search title, artist, genres") self.search_bar.setMaximumWidth(200) self.toolbar.addWidget(self.search_bar) self.toolbar.addSeparator() settings_icon = QIcon(gui_constants.SETTINGS_PATH) settings_action = QAction(settings_icon, "Set&tings", self) self.toolbar.addAction(settings_action) self.addToolBar(self.toolbar) spacer_end = QWidget() # aligns About action properly spacer_end.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_end) def setCurrentIndex(self, number, index=None): """Changes the current display view. Params: number <- int (0 for manga view, 1 for chapter view Optional: index <- QModelIndex for chapter view Note: 0-based indexing """ if index is not None: self.chapter_info_view.display_manga(index) self.display.setCurrentIndex(number) else: self.display.setCurrentIndex(number) # TODO: Improve this so that it adds to the series dialog, # so user can edit data before inserting (make it a choice) def populate(self): "Populates the database with series from local drive'" msgbox = QMessageBox() msgbox.setText("<font color='red'><b>Use with care.</b></font> Choose a folder containing all your series'.") msgbox.setInformativeText("Oniichan, are you sure you want to do this?") msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msgbox.setDefaultButton(QMessageBox.No) if msgbox.exec() == QMessageBox.Yes: path = QFileDialog.getExistingDirectory(None, "Choose a folder containing your series'") if len(path) is not 0: data_thread = QThread() loading_thread = QThread() loading = misc.Loading() if not loading.ON: misc.Loading.ON = True fetch_instance = fetch.Fetch() fetch_instance.series_path = path loading.show() def finished(status): if status: self.manga_list_view.series_model.populate_data() # TODO: make it spawn a dialog instead (from utils.py or misc.py) if loading.progress.maximum() == loading.progress.value(): misc.Loading.ON = False loading.hide() data_thread.quit else: loading.setText("<font color=red>An error occured. Try restarting..</font>") loading.progress.setStyleSheet("background-color:red") data_thread.quit def fetch_deleteLater(): try: fetch_instance.deleteLater except NameError: pass def thread_deleteLater(): #NOTE: Isn't this bad? data_thread.deleteLater def a_progress(prog): loading.progress.setValue(prog) loading.setText("Searching on local disk...\n(Will take a while on first time)") fetch_instance.moveToThread(data_thread) fetch_instance.DATA_COUNT.connect(loading.progress.setMaximum) fetch_instance.PROGRESS.connect(a_progress) data_thread.started.connect(fetch_instance.local) fetch_instance.FINISHED.connect(finished) fetch_instance.FINISHED.connect(fetch_deleteLater) fetch_instance.FINISHED.connect(thread_deleteLater) data_thread.start()
class JobbiUI(QMainWindow): def __init__(self, parent=None): super().__init__(parent) # Init window self._setupWindow() # List containing custom job add widgets self.addElements = [] # Init UI and create GUI components self._createToolBar() self._setupUI() self._createStatusBar() def insertJobs(self, adds): if adds is not None: adds = al.shuffle_adds(adds) adds.reverse() for add in adds: self.insertJobElement(add) # Insert job add objects def insertJobElement(self, jobAdd): element = _JobAddElement(jobAdd.add_heading, jobAdd.add_content, jobAdd.company, jobAdd.company_url, jobAdd.add_url, jobAdd.add_owner) # The first add element will have the index 0, with the current layout, # thus to insert new adds add the top of the list the insertWidget() # method is used insted of addWidget() self.generalLayout.insertWidget(0, element) self.addElements.append(element) def getIndices(self): for el in self.addElements: print(self.generalLayout.indexOf(el)) def updateStatus(self, msg): self.status.showMessage(msg) self.update() def _setupUI(self): # Set central widget to be a scroll area self.mainScrollArea = QScrollArea(self) self.setCentralWidget(self.mainScrollArea) # Main job-add layout self.baseWidget = QWidget() self.generalLayout = QVBoxLayout() self.generalLayout.setContentsMargins(100, 20, 100, 20) self.baseWidget.setLayout(self.generalLayout) # Point baseWidget to the main scroll area self.mainScrollArea.setWidget(self.baseWidget) self.mainScrollArea.setWidgetResizable(True) def _createToolBar(self): # Define main toolbar self.toolbar = QToolBar("Main toolbar") self.addToolBar(self.toolbar) self.toolbar.setIconSize(QSize(22, 22)) self.toolbar.setFixedHeight(36) # Define action to be included in the toolbar and add them to it self.settingsAction = QAction(QIcon(str(img_path / "build-24px.svg")), "Settings", self) self.updateAction = QAction( QIcon(str(img_path / "cloud_download-24px.svg")), "Load adds", self) self.toolbar.addAction(self.settingsAction) self.toolbar.addAction(self.updateAction) def _createStatusBar(self): self.status = QStatusBar() self.status.showMessage("Welcome") self.setStatusBar(self.status) def _setupWindow(self): self.setWindowTitle('Jobbi') self.setGeometry(0, 0, 990, 660) self.setMinimumWidth(800) self.setMinimumHeight(400) # Center main window qt_rectangle = self.frameGeometry() qt_rectangle.moveCenter(QDesktopWidget().availableGeometry().center()) self.move(qt_rectangle.topLeft()) # Set window background color self.setAutoFillBackground(True) p = self.palette() p.setColor(self.backgroundRole(), Qt.white) self.setPalette(p)
def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.browser = QWebEngineView() self.browser.setUrl(QUrl("https://www.stackoverflow.com")) self.browser.urlChanged.connect(self.update_urlbar) self.browser.loadFinished.connect(self.update_title) self.setCentralWidget(self.browser) self.status = QStatusBar() self.setStatusBar(self.status) ##################################################### navtb2 = QToolBar() navtb2.setIconSize(QSize(16, 16)) navtb2.setFixedHeight(50) navtb2.setIconSize(QSize(60, 60)) self.query_bar = QLineEdit(self) self.query_bar.setFixedHeight(45) f = self.query_bar.font() f.setPointSize(20) # sets the size to 27 self.query_bar.setFont(f) self.query_bar.setPlaceholderText("Powered by IBM Cloud...") self.query_bar.returnPressed.connect(self.process_query) navtb2.addWidget(self.query_bar) self.back_btn = QAction(QIcon(os.path.join('images', 'arrow-180.png')), "Previous Answer", self) self.back_btn.setStatusTip("Back to previous page") self.back_btn.triggered.connect(self.prev_answer) self.back_btn.setDisabled(True) navtb2.addAction(self.back_btn) self.next_btn = QAction(QIcon(os.path.join('images', 'arrow-000.png')), "Next Answer", self) self.next_btn.setStatusTip("Forward to next page") self.next_btn.triggered.connect(self.next_answer) self.next_btn.setDisabled(True) navtb2.addAction(self.next_btn) self.addToolBar(navtb2) self.addToolBarBreak() ##################################################### navtb = QToolBar("Navigation") navtb.setIconSize(QSize(16, 16)) self.addToolBar(navtb) reload_btn = QAction(QIcon(os.path.join('images', 'arrow-circle-315.png')), "Reload", self) reload_btn.setStatusTip("Reload page") reload_btn.triggered.connect(self.browser.reload) navtb.addAction(reload_btn) home_btn = QAction(QIcon(os.path.join('images', 'home.png')), "Home", self) home_btn.setStatusTip("Go home") home_btn.triggered.connect(self.navigate_home) navtb.addAction(home_btn) navtb.addSeparator() self.httpsicon = QLabel() # Yes, really! self.httpsicon.setPixmap(QPixmap(os.path.join('images', 'lock-nossl.png'))) navtb.addWidget(self.httpsicon) self.urlbar = QLineEdit() self.urlbar.returnPressed.connect(self.navigate_to_url) navtb.addWidget(self.urlbar) stop_btn = QAction(QIcon(os.path.join('images', 'cross-circle.png')), "Stop", self) stop_btn.setStatusTip("Stop loading current page") stop_btn.triggered.connect(self.browser.stop) navtb.addAction(stop_btn) # Uncomment to disable native menubar on Mac # self.menuBar().setNativeMenuBar(False) file_menu = self.menuBar().addMenu("&File") open_file_action = QAction(QIcon(os.path.join('images', 'disk--arrow.png')), "Open file...", self) open_file_action.setStatusTip("Open from file") open_file_action.triggered.connect(self.open_file) file_menu.addAction(open_file_action) save_file_action = QAction(QIcon(os.path.join('images', 'disk--pencil.png')), "Save Page As...", self) save_file_action.setStatusTip("Save current page to file") save_file_action.triggered.connect(self.save_file) file_menu.addAction(save_file_action) print_action = QAction(QIcon(os.path.join('images', 'printer.png')), "Print...", self) print_action.setStatusTip("Print current page") print_action.triggered.connect(self.print_page) file_menu.addAction(print_action) help_menu = self.menuBar().addMenu("&Help") about_action = QAction(QIcon(os.path.join('images', 'question.png')), "About CRIO", self) about_action.setStatusTip("Find out more about CRIO") # Hungry! about_action.triggered.connect(self.about) help_menu.addAction(about_action) navigate_mozarella_action = QAction(QIcon(os.path.join('images', 'lifebuoy.png')), "CRIO Homepage", self) navigate_mozarella_action.setStatusTip("Go to CRIO Homepage") navigate_mozarella_action.triggered.connect(self.navigate_mozarella) help_menu.addAction(navigate_mozarella_action) self.show() self.setWindowIcon(QIcon(os.path.join('images', 'ma-icon-64.png')))
class JavaTracePanel(QWidget): def __init__(self, app, *__args): super().__init__(app) self.app = app self.app.dwarf.onJavaTraceEvent.connect(self.on_event) self.app.dwarf.onEnumerateJavaClassesStart.connect( self.on_enumeration_start) self.app.dwarf.onEnumerateJavaClassesMatch.connect( self.on_enumeration_match) self.app.dwarf.onEnumerateJavaClassesComplete.connect( self.on_enumeration_complete) self.tracing = False self.trace_classes = [] self.trace_depth = 0 layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self._record_icon = QIcon( utils.resource_path('assets/icons/record.png')) self._pause_icon = QIcon(utils.resource_path('assets/icons/pause.png')) self._stop_icon = QIcon(utils.resource_path('assets/icons/stop.png')) self._tool_bar = QToolBar() self._tool_bar.setFixedHeight(16) self._tool_bar.setContentsMargins(1, 1, 1, 1) self._tool_bar.addAction(self._record_icon, 'Record', self.start_trace) self._tool_bar.addAction(self._pause_icon, 'Pause', self.pause_trace) self._tool_bar.addAction(self._stop_icon, 'Stop', self.stop_trace) self._tool_bar.addSeparator() self._entries_lbl = QLabel('Entries: 0') self._entries_lbl.setAlignment(Qt.AlignRight) self._entries_lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self._tool_bar.addWidget(self._entries_lbl) layout.addWidget(self._tool_bar) self.setup_splitter = QSplitter() self.events_list = JavaTraceView(self) self.events_list.setVisible(False) self.trace_list = QListWidget() self.class_list = QListWidget() self.trace_list.itemDoubleClicked.connect(self.trace_list_double_click) self.class_list.setContextMenuPolicy(Qt.CustomContextMenu) self.class_list.customContextMenuRequested.connect( self.show_class_list_menu) self.class_list.itemDoubleClicked.connect(self.class_list_double_click) self.current_class_search = '' bar = QScrollBar() bar.setFixedWidth(0) bar.setFixedHeight(0) self.trace_list.setHorizontalScrollBar(bar) bar = QScrollBar() bar.setFixedWidth(0) bar.setFixedHeight(0) self.class_list.setHorizontalScrollBar(bar) self.setup_splitter.addWidget(self.trace_list) self.setup_splitter.addWidget(self.class_list) self.setup_splitter.setHandleWidth(1) layout.addWidget(self.setup_splitter) layout.addWidget(self.events_list) self.setLayout(layout) def class_list_double_click(self, item): try: if self.trace_classes.index(item.text()) >= 0: return except: pass self.trace_classes.append(item.text()) q = NotEditableListWidgetItem(item.text()) self.trace_list.addItem(q) self.trace_list.sortItems() def on_enumeration_start(self): self.class_list.clear() def on_enumeration_match(self, java_class): try: if PREFIXED_CLASS.index(java_class) >= 0: try: if self.trace_classes.index(java_class) >= 0: return except: pass q = NotEditableListWidgetItem(java_class) self.trace_list.addItem(q) self.trace_classes.append(java_class) except: pass q = NotEditableListWidgetItem(java_class) self.class_list.addItem(q) def on_enumeration_complete(self): self.class_list.sortItems() self.trace_list.sortItems() def on_event(self, data): trace, event, clazz, data = data if trace == 'java_trace': self.events_list.add_event({ 'event': event, 'class': clazz, 'data': data.replace(',', ', ') }) self._entries_lbl.setText('Events: %d' % len(self.events_list.data)) def pause_trace(self): self.app.dwarf.dwarf_api('stopJavaTracer') self.tracing = False def search(self): accept, input = InputDialog.input( self.app, hint='Search', input_content=self.current_class_search, placeholder='Search something...') if accept: self.current_class_search = input.lower() for i in range(0, self.class_list.count()): try: if self.class_list.item(i).text().lower().index( self.current_class_search.lower()) >= 0: self.class_list.setRowHidden(i, False) except: self.class_list.setRowHidden(i, True) def show_class_list_menu(self, pos): menu = QMenu() search = menu.addAction('Search') action = menu.exec_(self.class_list.mapToGlobal(pos)) if action: if action == search: self.search() def start_trace(self): self.app.dwarf.dwarf_api('startJavaTracer', [self.trace_classes]) self.trace_depth = 0 self.tracing = True self.setup_splitter.setVisible(False) self.events_list.setVisible(True) def stop_trace(self): self.app.dwarf.dwarf_api('stopJavaTracer') self.tracing = False self.setup_splitter.setVisible(True) self.events_list.setVisible(False) self.events_list.clear() def trace_list_double_click(self, item): try: index = self.trace_classes.index(item.text()) except: return if index < 0: return self.trace_classes.pop(index) self.trace_list.takeItem(self.trace_list.row(item)) def keyPressEvent(self, event): if event.modifiers() & Qt.ControlModifier: if event.key() == Qt.Key_F: self.search() super(JavaTracePanel, self).keyPressEvent(event)
class AppWindow(QMainWindow): "The application's main window" def __init__(self): super().__init__() self.initUI() self.start_up() QTimer.singleShot(3000, self._check_update) def init_watchers(self): def remove_gallery(g): index = self.manga_list_view.find_index(g.id) if index: self.manga_list_view.remove_gallery([index]) def create_gallery(path): g_dia = gallerydialog.GalleryDialog(self, path) g_dia.SERIES.connect(self.manga_list_view.gallery_model.addRows) g_dia.show() def update_gallery(g): index = self.manga_list_view.find_index(g.id) if index: self.manga_list_view.replace_edit_gallery([g], index.row()) else: log_e("Could not find gallery to update from Watcher") def created(path): c_popup = file_misc.CreatedPopup(path, self) c_popup.ADD_SIGNAL.connect(create_gallery) def modified(path, gallery): mod_popup = file_misc.ModifiedPopup(path, gallery, self) def deleted(path, gallery): d_popup = file_misc.DeletedPopup(path, gallery, self) d_popup.UPDATE_SIGNAL.connect(update_gallery) d_popup.REMOVE_SIGNAL.connect(remove_gallery) def moved(new_path, gallery): mov_popup = file_misc.MovedPopup(new_path, gallery, self) mov_popup.UPDATE_SIGNAL.connect(update_gallery) self.watchers = file_misc.Watchers() self.watchers.gallery_handler.CREATE_SIGNAL.connect(created) self.watchers.gallery_handler.MODIFIED_SIGNAL.connect(modified) self.watchers.gallery_handler.MOVED_SIGNAL.connect(moved) self.watchers.gallery_handler.DELETED_SIGNAL.connect(deleted) if gui_constants.LOOK_NEW_GALLERY_STARTUP: self.notification_bar.add_text("Looking for new galleries...") try: class ScanDir(QObject): final_paths_and_galleries = pyqtSignal(list, list) def __init__(self, model_data, parent=None): super().__init__(parent) self.model_data = model_data def scan_dirs(self): db_data = self.model_data paths = [] for g in range(len(db_data)): paths.append(os.path.normcase(db_data[g].path)) contents = [] case_path = [] # needed for tile and artist parsing... e.g to avoid lowercase for m_path in gui_constants.MONITOR_PATHS: for p in os.listdir(m_path): abs_p = os.path.join(m_path, p) if os.path.isdir(abs_p) or p.endswith(utils.ARCHIVE_FILES): case_path.append(abs_p) contents.append(os.path.normcase(abs_p)) paths = sorted(paths) new_galleries = [] for c, x in enumerate(contents): y = utils.b_search(paths, x) if not y: # (path, number for case_path) new_galleries.append((x, c)) galleries = [] final_paths = [] if new_galleries: for g in new_galleries: gallery = gallerydb.Gallery() try: gallery.profile = utils.get_gallery_img(g[0]) except: gallery.profile = gui_constants.NO_IMAGE_PATH parser_dict = utils.title_parser(os.path.split(case_path[g[1]])[1]) gallery.title = parser_dict["title"] gallery.artist = parser_dict["artist"] galleries.append(gallery) final_paths.append(case_path[g[1]]) self.final_paths_and_galleries.emit(final_paths, galleries) # if gui_constants.LOOK_NEW_GALLERY_AUTOADD: # QTimer.singleShot(10000, self.gallery_populate(final_paths)) # return def show_new_galleries(final_paths, galleries): if final_paths and galleries: if gui_constants.LOOK_NEW_GALLERY_AUTOADD: self.gallery_populate(final_paths) else: if len(galleries) == 1: self.notification_bar.add_text( "{} new gallery was discovered in one of your monitored directories".format( len(galleries) ) ) else: self.notification_bar.add_text( "{} new galleries were discovered in one of your monitored directories".format( len(galleries) ) ) text = ( "These new galleries were discovered! Do you want to add them?" if len(galleries) > 1 else "This new gallery was discovered! Do you want to add it?" ) g_popup = file_misc.GalleryPopup((text, galleries), self) buttons = g_popup.add_buttons("Add", "Close") def populate_n_close(): self.gallery_populate(final_paths) g_popup.close() buttons[0].clicked.connect(populate_n_close) buttons[1].clicked.connect(g_popup.close) thread = QThread(self) self.scan_inst = ScanDir(self.manga_list_view.gallery_model._data) self.scan_inst.moveToThread(thread) self.scan_inst.final_paths_and_galleries.connect(show_new_galleries) self.scan_inst.final_paths_and_galleries.connect(lambda a: self.scan_inst.deleteLater()) thread.started.connect(self.scan_inst.scan_dirs) thread.finished.connect(thread.deleteLater) thread.start() except: self.notification_bar.add_text( "An error occured while attempting to scan for new galleries. Check happypanda.log." ) log.exception("An error occured while attempting to scan for new galleries.") def start_up(self): def normalize_first_time(): settings.set(2, "Application", "first time level") def done(): self.manga_list_view.gallery_model.init_data() if gui_constants.ENABLE_MONITOR and gui_constants.MONITOR_PATHS and all(gui_constants.MONITOR_PATHS): self.init_watchers() if gui_constants.FIRST_TIME_LEVEL != 2: normalize_first_time() if gui_constants.FIRST_TIME_LEVEL < 2: class FirstTime(file_misc.BasePopup): def __init__(self, parent=None): super().__init__(parent) main_layout = QVBoxLayout() info_lbl = QLabel( "Hi there! Some big changes are about to occur!\n" + "Please wait.. This will take at most a few minutes.\n" + "If not then try restarting the application." ) info_lbl.setAlignment(Qt.AlignCenter) main_layout.addWidget(info_lbl) prog = QProgressBar(self) prog.setMinimum(0) prog.setMaximum(0) prog.setTextVisible(False) main_layout.addWidget(prog) main_layout.addWidget(QLabel("Note: This popup will close itself when everything is ready")) self.main_widget.setLayout(main_layout) ft_widget = FirstTime(self) log_i("Invoking first time level 2") bridge = gallerydb.Bridge() thread = QThread(self) thread.setObjectName("Startup") bridge.moveToThread(thread) thread.started.connect(bridge.rebuild_galleries) bridge.DONE.connect(ft_widget.close) bridge.DONE.connect(self.setEnabled) bridge.DONE.connect(done) bridge.DONE.connect(bridge.deleteLater) thread.finished.connect(thread.deleteLater) thread.start() ft_widget.adjustSize() ft_widget.show() self.setEnabled(False) else: done() def initUI(self): self.center = QWidget() self.display = QStackedLayout() self.center.setLayout(self.display) # init the manga view variables self.manga_display() log_d("Create manga display: OK") # init the chapter view variables # self.chapter_display() self.m_l_view_index = self.display.addWidget(self.manga_list_main) self.m_t_view_index = self.display.addWidget(self.manga_table_view) # init toolbar self.init_toolbar() log_d("Create toolbar: OK") # init status bar self.init_stat_bar() log_d("Create statusbar: OK") self.system_tray = misc.SystemTray(QIcon(gui_constants.APP_ICO_PATH), self) gui_constants.SYSTEM_TRAY = self.system_tray tray_menu = QMenu(self) self.system_tray.setContextMenu(tray_menu) self.system_tray.setToolTip("Happypanda {}".format(gui_constants.vs)) tray_quit = QAction("Quit", tray_menu) tray_menu.addAction(tray_quit) tray_quit.triggered.connect(self.close) self.system_tray.show() log_d("Create system tray: OK") # self.display.addWidget(self.chapter_main) self.setCentralWidget(self.center) self.setWindowTitle("Happypanda") self.setWindowIcon(QIcon(gui_constants.APP_ICO_PATH)) props = settings.win_read(self, "AppWindow") if props.resize: x, y = props.resize self.resize(x, y) else: self.resize(gui_constants.MAIN_W, gui_constants.MAIN_H) posx, posy = props.pos self.move(posx, posy) self.show() log_d("Show window: OK") self.notification_bar = misc.NotificationOverlay(self) p = self.toolbar.pos() self.notification_bar.move(p.x(), p.y() + self.toolbar.height()) self.notification_bar.resize(self.width()) gui_constants.NOTIF_BAR = self.notification_bar log_d("Create notificationbar: OK") log_d("Window Create: OK") def _check_update(self): class upd_chk(QObject): UPDATE_CHECK = pyqtSignal(str) def __init__(self, **kwargs): super().__init__(**kwargs) def fetch_vs(self): import requests import time try: log_d("Checking Update") time.sleep(1.5) r = requests.get( "https://raw.githubusercontent.com/Pewpews/happypanda/master/VS.txt", verify="cacert.pem" ) a = r.text vs = a.strip() self.UPDATE_CHECK.emit(vs) except: log.exception("Checking Update: FAIL") self.UPDATE_CHECK.emit("this is a very long text which is is sure to be over limit") def check_update(vs): log_i("Received version: {}\nCurrent version: {}".format(vs, gui_constants.vs)) if vs != gui_constants.vs: if len(vs) < 10: self.notification_bar.add_text( "Version {} of Happypanda is".format(vs) + " available. Click here to update!", False ) self.notification_bar.clicked.connect( lambda: utils.open_web_link("https://github.com/Pewpews/happypanda/releases") ) self.notification_bar.set_clickable(True) else: self.notification_bar.add_text("An error occurred while checking for new version") self.update_instance = upd_chk() thread = QThread(self) self.update_instance.moveToThread(thread) thread.started.connect(self.update_instance.fetch_vs) self.update_instance.UPDATE_CHECK.connect(check_update) self.update_instance.UPDATE_CHECK.connect(self.update_instance.deleteLater) thread.finished.connect(thread.deleteLater) thread.start() def _web_metadata_picker(self, gallery, title_url_list, queue, parent=None): if not parent: parent = self text = "Which gallery do you want to extract metadata from?" s_gallery_popup = misc.SingleGalleryChoices(gallery, title_url_list, text, parent) s_gallery_popup.USER_CHOICE.connect(queue.put) def get_metadata(self, gal=None): thread = QThread(self) thread.setObjectName("App.get_metadata") fetch_instance = fetch.Fetch() if gal: galleries = [gal] else: if gui_constants.CONTINUE_AUTO_METADATA_FETCHER: galleries = [g for g in self.manga_list_view.gallery_model._data if not g.exed] else: galleries = self.manga_list_view.gallery_model._data if not galleries: self.notification_bar.add_text("Looks like we've already gone through all galleries!") return None fetch_instance.galleries = galleries self.notification_bar.begin_show() fetch_instance.moveToThread(thread) def done(status): self.notification_bar.end_show() fetch_instance.deleteLater() fetch_instance.GALLERY_PICKER.connect(self._web_metadata_picker) fetch_instance.GALLERY_EMITTER.connect(self.manga_list_view.replace_edit_gallery) fetch_instance.AUTO_METADATA_PROGRESS.connect(self.notification_bar.add_text) thread.started.connect(fetch_instance.auto_web_metadata) fetch_instance.FINISHED.connect(done) thread.finished.connect(thread.deleteLater) thread.start() # def style_tooltip(self): # palette = QToolTip.palette() # palette.setColor() def init_stat_bar(self): self.status_bar = self.statusBar() self.status_bar.setMaximumHeight(20) self.status_bar.setSizeGripEnabled(False) self.stat_info = QLabel() self.stat_info.setIndent(5) self.sort_main = QAction("Asc", self) sort_menu = QMenu() self.sort_main.setMenu(sort_menu) s_by_title = QAction("Title", sort_menu) s_by_artist = QAction("Artist", sort_menu) sort_menu.addAction(s_by_title) sort_menu.addAction(s_by_artist) self.status_bar.addPermanentWidget(self.stat_info) # self.status_bar.addAction(self.sort_main) self.temp_msg = QLabel() self.temp_timer = QTimer() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.connect(self.stat_row_info) self.manga_list_view.gallery_model.STATUSBAR_MSG.connect(self.stat_temp_msg) self.manga_list_view.STATUS_BAR_MSG.connect(self.stat_temp_msg) self.manga_table_view.STATUS_BAR_MSG.connect(self.stat_temp_msg) self.stat_row_info() def stat_temp_msg(self, msg): self.temp_timer.stop() self.temp_msg.setText(msg) self.status_bar.addWidget(self.temp_msg) self.temp_timer.timeout.connect(self.temp_msg.clear) self.temp_timer.setSingleShot(True) self.temp_timer.start(5000) def stat_row_info(self): r = self.manga_list_view.model().rowCount() t = self.manga_list_view.gallery_model._data_count self.stat_info.setText("Loaded {} of {} ".format(r, t)) def manga_display(self): "initiates the manga view" # list view self.manga_list_main = QWidget() # self.manga_list_main.setContentsMargins(-10, -12, -10, -10) self.manga_list_main.setContentsMargins(10, -9, -10, -10) # x, y, inverted_width, inverted_height self.manga_list_layout = QHBoxLayout() self.manga_list_main.setLayout(self.manga_list_layout) self.manga_list_view = gallery.MangaView(self) self.manga_list_view.clicked.connect(self.popup) self.manga_list_view.manga_delegate.POPUP.connect(self.popup) self.popup_window = self.manga_list_view.manga_delegate.popup_window self.manga_list_layout.addWidget(self.manga_list_view) # table view self.manga_table_main = QWidget() self.manga_table_layout = QVBoxLayout() self.manga_table_main.setLayout(self.manga_table_layout) self.manga_table_view = gallery.MangaTableView(self) self.manga_table_view.gallery_model = self.manga_list_view.gallery_model self.manga_table_view.sort_model = self.manga_list_view.sort_model self.manga_table_view.setModel(self.manga_table_view.sort_model) self.manga_table_view.sort_model.change_model(self.manga_table_view.gallery_model) self.manga_table_view.setColumnWidth(gui_constants.FAV, 20) self.manga_table_view.setColumnWidth(gui_constants.ARTIST, 200) self.manga_table_view.setColumnWidth(gui_constants.TITLE, 400) self.manga_table_view.setColumnWidth(gui_constants.TAGS, 300) self.manga_table_view.setColumnWidth(gui_constants.TYPE, 60) self.manga_table_view.setColumnWidth(gui_constants.CHAPTERS, 60) self.manga_table_view.setColumnWidth(gui_constants.LANGUAGE, 100) self.manga_table_view.setColumnWidth(gui_constants.LINK, 400) self.manga_table_layout.addWidget(self.manga_table_view) def search(self, srch_string): case_ins = srch_string.lower() if not gui_constants.ALLOW_SEARCH_REGEX: remove = "^$*+?{}\\|()[]" for x in remove: if x == "[" or x == "]": continue else: case_ins = case_ins.replace(x, ".") else: try: re.compile(case_ins) except re.error: return self.manga_list_view.sort_model.search(case_ins) def popup(self, index): if not self.popup_window.isVisible(): m_x = QCursor.pos().x() m_y = QCursor.pos().y() d_w = QDesktopWidget().width() d_h = QDesktopWidget().height() p_w = gui_constants.POPUP_WIDTH p_h = gui_constants.POPUP_HEIGHT index_rect = self.manga_list_view.visualRect(index) index_point = self.manga_list_view.mapToGlobal(index_rect.topRight()) # adjust so it doesn't go offscreen if d_w - m_x < p_w and d_h - m_y < p_h: # bottom self.popup_window.move(m_x - p_w + 5, m_y - p_h) elif d_w - m_x > p_w and d_h - m_y < p_h: self.popup_window.move(m_x + 5, m_y - p_h) elif d_w - m_x < p_w: self.popup_window.move(m_x - p_w + 5, m_y + 5) else: self.popup_window.move(index_point) self.popup_window.set_gallery(index.data(Qt.UserRole + 1)) self.popup_window.show() def favourite_display(self): "Switches to favourite display" if self.display.currentIndex() == self.m_l_view_index: self.manga_list_view.sort_model.fav_view() else: self.manga_table_view.sort_model.fav_view() def catalog_display(self): "Switches to catalog display" if self.display.currentIndex() == self.m_l_view_index: self.manga_list_view.sort_model.catalog_view() else: self.manga_table_view.sort_model.catalog_view() def settings(self): sett = settingsdialog.SettingsDialog(self) sett.scroll_speed_changed.connect(self.manga_list_view.updateGeometries) # sett.show() def init_toolbar(self): self.toolbar = QToolBar() self.toolbar.setFixedHeight(25) self.toolbar.setWindowTitle("Show") # text for the contextmenu # self.toolbar.setStyleSheet("QToolBar {border:0px}") # make it user defined? self.toolbar.setMovable(False) self.toolbar.setFloatable(False) # self.toolbar.setIconSize(QSize(20,20)) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) spacer_start = QWidget() # aligns the first actions properly spacer_start.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_start) favourite_view_icon = QIcon(gui_constants.STAR_BTN_PATH) favourite_view_action = QAction(favourite_view_icon, "Favorites", self) favourite_view_action.setToolTip("Show only favourite galleries") favourite_view_action.triggered.connect(self.favourite_display) # need lambda to pass extra args self.toolbar.addAction(favourite_view_action) catalog_view_icon = QIcon(gui_constants.HOME_BTN_PATH) catalog_view_action = QAction(catalog_view_icon, "Library", self) catalog_view_action.setToolTip("Show all your galleries") # catalog_view_action.setText("Catalog") catalog_view_action.triggered.connect(self.catalog_display) # need lambda to pass extra args self.toolbar.addAction(catalog_view_action) self.toolbar.addSeparator() gallery_menu = QMenu() gallery_action = QToolButton() gallery_action.setText("Gallery ") gallery_action.setPopupMode(QToolButton.InstantPopup) gallery_action.setToolTip("Contains various gallery related features") gallery_action.setMenu(gallery_menu) add_gallery_icon = QIcon(gui_constants.PLUS_PATH) gallery_action_add = QAction(add_gallery_icon, "Add gallery", self) gallery_action_add.triggered.connect(self.manga_list_view.SERIES_DIALOG.emit) gallery_action_add.setToolTip("Add a single gallery thoroughly") gallery_menu.addAction(gallery_action_add) add_more_action = QAction(add_gallery_icon, "Add galleries...", self) add_more_action.setStatusTip("Add galleries from different folders") add_more_action.triggered.connect(lambda: self.populate(True)) gallery_menu.addAction(add_more_action) populate_action = QAction(add_gallery_icon, "Populate from folder...", self) populate_action.setStatusTip("Populates the DB with galleries from a single folder") populate_action.triggered.connect(self.populate) gallery_menu.addAction(populate_action) gallery_menu.addSeparator() metadata_action = QAction("Get metadata for all galleries", self) metadata_action.triggered.connect(self.get_metadata) gallery_menu.addAction(metadata_action) self.toolbar.addWidget(gallery_action) self.toolbar.addSeparator() misc_action = QToolButton() misc_action.setText("Misc ") misc_action_menu = QMenu() misc_action.setMenu(misc_action_menu) misc_action.setPopupMode(QToolButton.InstantPopup) misc_action.setToolTip("Contains misc. features") misc_action_random = QAction("Open random gallery", misc_action_menu) misc_action_random.triggered.connect(self.manga_list_view.open_random_gallery) misc_action_menu.addAction(misc_action_random) duplicate_check_simple = QAction("Simple duplicate finder", misc_action_menu) duplicate_check_simple.triggered.connect(lambda: self.manga_list_view.duplicate_check()) misc_action_menu.addAction(duplicate_check_simple) self.toolbar.addWidget(misc_action) spacer_middle = QWidget() # aligns buttons to the right spacer_middle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer_middle) self.grid_toggle_g_icon = QIcon(gui_constants.GRID_PATH) self.grid_toggle_l_icon = QIcon(gui_constants.LIST_PATH) self.grid_toggle = QToolButton() if self.display.currentIndex() == self.m_l_view_index: self.grid_toggle.setIcon(self.grid_toggle_l_icon) else: self.grid_toggle.setIcon(self.grid_toggle_g_icon) self.grid_toggle.setObjectName("gridtoggle") self.grid_toggle.clicked.connect(self.toggle_view) self.toolbar.addWidget(self.grid_toggle) self.search_bar = misc.LineEdit() if gui_constants.SEARCH_AUTOCOMPLETE: completer = QCompleter(self) completer.setModel(self.manga_list_view.gallery_model) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setCompletionMode(QCompleter.PopupCompletion) completer.setCompletionRole(Qt.DisplayRole) completer.setCompletionColumn(gui_constants.TITLE) completer.setFilterMode(Qt.MatchContains) self.search_bar.setCompleter(completer) if gui_constants.SEARCH_ON_ENTER: self.search_bar.returnPressed.connect(lambda: self.search(self.search_bar.text())) else: self.search_bar.textChanged[str].connect(self.search) self.search_bar.setPlaceholderText("Search title, artist, namespace & tags") self.search_bar.setMinimumWidth(150) self.search_bar.setMaximumWidth(500) self.toolbar.addWidget(self.search_bar) self.toolbar.addSeparator() settings_icon = QIcon(gui_constants.SETTINGS_PATH) settings_action = QAction("Set&tings", self) settings_action.triggered.connect(self.settings) self.toolbar.addAction(settings_action) spacer_end = QWidget() # aligns About action properly spacer_end.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_end) self.addToolBar(self.toolbar) def toggle_view(self): """ Toggles the current display view """ if self.display.currentIndex() == self.m_l_view_index: self.display.setCurrentIndex(self.m_t_view_index) self.grid_toggle.setIcon(self.grid_toggle_g_icon) else: self.display.setCurrentIndex(self.m_l_view_index) self.grid_toggle.setIcon(self.grid_toggle_l_icon) # TODO: Improve this so that it adds to the gallery dialog, # so user can edit data before inserting (make it a choice) def populate(self, mixed=None): "Populates the database with gallery from local drive'" if mixed: gallery_view = misc.GalleryListView(self, True) gallery_view.SERIES.connect(self.gallery_populate) gallery_view.show() else: path = QFileDialog.getExistingDirectory(None, "Choose a folder containing your galleries") self.gallery_populate(path, True) def gallery_populate(self, path, validate=False): "Scans the given path for gallery to add into the DB" if len(path) is not 0: data_thread = QThread(self) data_thread.setObjectName("General gallery populate") loading = misc.Loading(self) if not loading.ON: misc.Loading.ON = True fetch_instance = fetch.Fetch() fetch_instance.series_path = path loading.show() def finished(status): def hide_loading(): loading.hide() if status: if len(status) != 0: def add_gallery(gallery_list): def append_to_model(x): self.manga_list_view.gallery_model.insertRows(x, None, len(x)) class A(QObject): done = pyqtSignal() prog = pyqtSignal(int) def __init__(self, obj, parent=None): super().__init__(parent) self.obj = obj self.galleries = [] def add_to_db(self): gui_constants.NOTIF_BAR.begin_show() gui_constants.NOTIF_BAR.add_text("Populating database...") for y, x in enumerate(self.obj): gui_constants.NOTIF_BAR.add_text( "Populating database {}/{}".format(y + 1, len(self.obj)) ) gallerydb.add_method_queue(gallerydb.GalleryDB.add_gallery_return, False, x) self.galleries.append(x) y += 1 self.prog.emit(y) append_to_model(self.galleries) gui_constants.NOTIF_BAR.end_show() self.done.emit() loading.progress.setMaximum(len(gallery_list)) a_instance = A(gallery_list) thread = QThread(self) thread.setObjectName("Database populate") def loading_show(): loading.setText("Populating database.\nPlease wait...") loading.show() def loading_hide(): loading.close() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit() def del_later(): try: a_instance.deleteLater() except NameError: pass a_instance.moveToThread(thread) a_instance.prog.connect(loading.progress.setValue) thread.started.connect(loading_show) thread.started.connect(a_instance.add_to_db) a_instance.done.connect(loading_hide) a_instance.done.connect(del_later) thread.finished.connect(thread.deleteLater) thread.start() data_thread.quit hide_loading() log_i("Populating DB from gallery folder: OK") if validate: gallery_list = misc.GalleryListView(self) gallery_list.SERIES.connect(add_gallery) for ser in status: gallery_list.add_gallery(ser, os.path.split(ser.path)[1]) # self.manga_list_view.gallery_model.populate_data() gallery_list.show() else: add_gallery(status) misc.Loading.ON = False else: log_d("No new gallery was found") loading.setText("No new gallery found") data_thread.quit misc.Loading.ON = False else: log_e("Populating DB from gallery folder: Nothing was added!") loading.setText("<font color=red>Nothing was added. Check happypanda_log for details..</font>") loading.progress.setStyleSheet("background-color:red;") data_thread.quit QTimer.singleShot(10000, loading.close) def fetch_deleteLater(): try: fetch_instance.deleteLater except NameError: pass def a_progress(prog): loading.progress.setValue(prog) loading.setText("Searching for galleries...") fetch_instance.moveToThread(data_thread) fetch_instance.DATA_COUNT.connect(loading.progress.setMaximum) fetch_instance.PROGRESS.connect(a_progress) data_thread.started.connect(fetch_instance.local) fetch_instance.FINISHED.connect(finished) fetch_instance.FINISHED.connect(fetch_deleteLater) data_thread.finished.connect(data_thread.deleteLater) data_thread.start() log_i("Populating DB from gallery folder") def resizeEvent(self, event): try: self.notification_bar.resize(event.size().width()) except AttributeError: pass return super().resizeEvent(event) def closeEvent(self, event): # watchers try: self.watchers.stop_all() except AttributeError: pass # settings settings.set(self.manga_list_view.current_sort, "General", "current sort") settings.win_save(self, "AppWindow") # temp dir try: for root, dirs, files in os.walk("temp", topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) log_d("Empty temp on exit: OK") except: log_d("Empty temp on exit: FAIL") # error err = sys.exc_info() if all(err): log_c("Last error before exit:\n{}\n{}\n{}".format(err[0], err[1], err[2])) else: log_d("Normal Exit App: OK") super().closeEvent(event) app = QApplication.instance() app.quit() sys.exit()
class AppWindow(QMainWindow): "The application's main window" move_listener = pyqtSignal() db_activity_checker = pyqtSignal() graphics_blur = QGraphicsBlurEffect() def __init__(self): super().__init__() app_constants.GENERAL_THREAD = QThread(self) app_constants.GENERAL_THREAD.finished.connect(app_constants.GENERAL_THREAD.deleteLater) app_constants.GENERAL_THREAD.start() self.setAcceptDrops(True) self.initUI() self.start_up() QTimer.singleShot(3000, self._check_update) self.setFocusPolicy(Qt.NoFocus) self.set_shortcuts() self.graphics_blur.setParent(self) #ex = settings.ExProperties() #d = pewnet.ExHenManager(ex.ipb_id, ex.ipb_pass) #item = d.from_gallery_url('http://exhentai.org/g/861957/02741dc584/') #def a(): print(item.file) #if not item.file: # item.file_rdy.connect(a) #else: # a() def set_shortcuts(self): quit = QShortcut(QKeySequence('Ctrl+Q'), self, self.close) def init_watchers(self): def remove_gallery(g): index = self.manga_list_view.find_index(g.id, True) if index: self.manga_list_view.remove_gallery([index]) def create_gallery(path): g_dia = gallerydialog.GalleryDialog(self, path) g_dia.SERIES.connect(self.manga_list_view.gallery_model.addRows) g_dia.show() def update_gallery(g): index = self.manga_list_view.find_index(g.id) if index: self.manga_list_view.replace_edit_gallery([g], index.row()) else: log_e('Could not find gallery to update from watcher') def created(path): c_popup = io_misc.CreatedPopup(path, self) c_popup.ADD_SIGNAL.connect(create_gallery) def modified(path, gallery): mod_popup = io_misc.ModifiedPopup(path, gallery, self) def deleted(path, gallery): d_popup = io_misc.DeletedPopup(path, gallery, self) d_popup.UPDATE_SIGNAL.connect(update_gallery) d_popup.REMOVE_SIGNAL.connect(remove_gallery) def moved(new_path, gallery): mov_popup = io_misc.MovedPopup(new_path, gallery, self) mov_popup.UPDATE_SIGNAL.connect(update_gallery) self.watchers = io_misc.Watchers() self.watchers.gallery_handler.CREATE_SIGNAL.connect(created) self.watchers.gallery_handler.MODIFIED_SIGNAL.connect(modified) self.watchers.gallery_handler.MOVED_SIGNAL.connect(moved) self.watchers.gallery_handler.DELETED_SIGNAL.connect(deleted) admin_db_method_invoker = pyqtSignal(str) def start_up(self): hello = ["Hello!", "Hi!", "Onii-chan!", "Senpai!", "Hisashiburi!", "Welcome!", "Okaerinasai!", "Welcome back!", "Hajimemashite!"] self.notification_bar.add_text("{} Please don't hesitate to report any bugs you find.".format(hello[random.randint(0, len(hello)-1)])+ " Go to Settings -> About -> Bug Reporting for more info!") level = 5 def normalize_first_time(): settings.set(level, 'Application', 'first time level') settings.save() def done(status=True): gallerydb.DatabaseEmitter.RUN = True if app_constants.FIRST_TIME_LEVEL != level: normalize_first_time() if app_constants.ENABLE_MONITOR and\ app_constants.MONITOR_PATHS and all(app_constants.MONITOR_PATHS): self.init_watchers() if app_constants.LOOK_NEW_GALLERY_STARTUP: if self.manga_list_view.gallery_model.db_emitter.count == app_constants.GALLERY_DATA: self.scan_for_new_galleries() else: self.manga_list_view.gallery_model.db_emitter.DONE.connect(self.scan_for_new_galleries) self.download_manager = pewnet.Downloader() app_constants.DOWNLOAD_MANAGER = self.download_manager self.download_manager.start_manager(4) if app_constants.FIRST_TIME_LEVEL < 4: log_i('Invoking first time level {}'.format(4)) settings.set([], 'Application', 'monitor paths') settings.set([], 'Application', 'ignore paths') app_constants.MONITOR_PATHS = [] app_constants.IGNORE_PATHS = [] settings.save() done() elif app_constants.FIRST_TIME_LEVEL < 5: log_i('Invoking first time level {}'.format(5)) app_widget = misc.ApplicationPopup(self) app_widget.note_info.setText("<font color='red'>IMPORTANT:</font> Application restart is required when done") app_widget.restart_info.hide() self.admin_db = gallerydb.AdminDB() self.admin_db.moveToThread(app_constants.GENERAL_THREAD) self.admin_db.DONE.connect(done) self.admin_db.DONE.connect(lambda: app_constants.NOTIF_BAR.add_text("Application requires a restart")) self.admin_db.DONE.connect(self.admin_db.deleteLater) self.admin_db.DATA_COUNT.connect(app_widget.prog.setMaximum) self.admin_db.PROGRESS.connect(app_widget.prog.setValue) self.admin_db_method_invoker.connect(self.admin_db.rebuild_db) self.admin_db_method_invoker.connect(app_widget.show) app_widget.adjustSize() db_p = os.path.join(os.path.split(database.db_constants.DB_PATH)[0], 'sadpanda.db') self.admin_db_method_invoker.emit(db_p) else: done() def initUI(self): self.center = QWidget() self.display = QStackedLayout() self._main_layout = QVBoxLayout() self._main_layout.setSpacing(0) self._main_layout.setContentsMargins(0,0,0,0) self._main_layout.addLayout(self.display) self.center.setLayout(self._main_layout) # init the manga view variables self.manga_display() log_d('Create manga display: OK') # init the chapter view variables #self.chapter_display() self.m_l_view_index = self.display.addWidget(self.manga_list_view) self.m_t_view_index = self.display.addWidget(self.manga_table_view) self.download_window = io_misc.GalleryDownloader(self) self.download_window.hide() # init toolbar self.init_toolbar() log_d('Create toolbar: OK') # init status bar self.init_stat_bar() log_d('Create statusbar: OK') self.tags_treeview = None if app_constants.TAGS_TREEVIEW_ON_START: def tags_tree_none(): self.tags_treeview = None self.tags_treeview = misc_db.DBOverview(self, True) self.tags_treeview.about_to_close.connect(tags_tree_none) self.tags_treeview.show() self.system_tray = misc.SystemTray(QIcon(app_constants.APP_ICO_PATH), self) app_constants.SYSTEM_TRAY = self.system_tray tray_menu = QMenu(self) self.system_tray.setContextMenu(tray_menu) self.system_tray.setToolTip('Happypanda {}'.format(app_constants.vs)) tray_quit = QAction('Quit', tray_menu) tray_update = tray_menu.addAction('Check for update') tray_update.triggered.connect(self._check_update) tray_menu.addAction(tray_quit) tray_quit.triggered.connect(self.close) self.system_tray.show() def tray_activate(r=None): if not r or r == QSystemTrayIcon.Trigger: self.showNormal() self.activateWindow() self.system_tray.messageClicked.connect(tray_activate) self.system_tray.activated.connect(tray_activate) log_d('Create system tray: OK') #self.display.addWidget(self.chapter_main) self.setCentralWidget(self.center) self.setWindowIcon(QIcon(app_constants.APP_ICO_PATH)) props = settings.win_read(self, 'AppWindow') if props.resize: x, y = props.resize self.resize(x, y) else: self.resize(app_constants.MAIN_W, app_constants.MAIN_H) posx, posy = props.pos self.move(posx, posy) self.init_spinners() self.show() log_d('Show window: OK') self.notification_bar = misc.NotificationOverlay(self) p = self.toolbar.pos() self.notification_bar.move(p.x(), p.y()+self.toolbar.height()) self.notification_bar.resize(self.width()) app_constants.NOTIF_BAR = self.notification_bar log_d('Create notificationbar: OK') log_d('Window Create: OK') def _check_update(self): class upd_chk(QObject): UPDATE_CHECK = pyqtSignal(str) def __init__(self, **kwargs): super().__init__(**kwargs) def fetch_vs(self): import requests import time log_d('Checking Update') time.sleep(1.5) try: if os.path.exists('cacert.pem'): r = requests.get("https://raw.githubusercontent.com/Pewpews/happypanda/master/VS.txt", verify='cacert.pem') else: r = requests.get("https://raw.githubusercontent.com/Pewpews/happypanda/master/VS.txt") a = r.text vs = a.strip() self.UPDATE_CHECK.emit(vs) except: log.exception('Checking Update: FAIL') self.UPDATE_CHECK.emit('this is a very long text which is sure to be over limit') def check_update(vs): log_i('Received version: {}\nCurrent version: {}'.format(vs, app_constants.vs)) if vs != app_constants.vs: if len(vs) < 10: self.notification_bar.begin_show() self.notification_bar.add_text("Version {} of Happypanda is".format(vs)+ " available. Click here to update!", False) self.notification_bar.clicked.connect(lambda: utils.open_web_link( 'https://github.com/Pewpews/happypanda/releases')) self.notification_bar.set_clickable(True) else: self.notification_bar.add_text("An error occurred while checking for new version") self.update_instance = upd_chk() thread = QThread(self) self.update_instance.moveToThread(thread) thread.started.connect(self.update_instance.fetch_vs) self.update_instance.UPDATE_CHECK.connect(check_update) self.update_instance.UPDATE_CHECK.connect(self.update_instance.deleteLater) thread.finished.connect(thread.deleteLater) thread.start() def _web_metadata_picker(self, gallery, title_url_list, queue, parent=None): if not parent: parent = self text = "Which gallery do you want to extract metadata from?" s_gallery_popup = misc.SingleGalleryChoices(gallery, title_url_list, text, parent) s_gallery_popup.USER_CHOICE.connect(queue.put) def get_metadata(self, gal=None): metadata_spinner = misc.Spinner(self) def move_md_spinner(): metadata_spinner.update_move( QPoint( self.pos().x()+self.width()-65, self.pos().y()+self.toolbar.height()+55)) metadata_spinner.set_text("Metadata") metadata_spinner.set_size(55) metadata_spinner.move(QPoint(self.pos().x()+self.width()-65, self.pos().y()+self.toolbar.height()+55)) self.move_listener.connect(move_md_spinner) thread = QThread(self) thread.setObjectName('App.get_metadata') fetch_instance = fetch.Fetch() if gal: if not isinstance(gal, list): galleries = [gal] else: galleries = gal else: if app_constants.CONTINUE_AUTO_METADATA_FETCHER: galleries = [g for g in self.manga_list_view.gallery_model._data if not g.exed] else: galleries = self.manga_list_view.gallery_model._data if not galleries: self.notification_bar.add_text('Looks like we\'ve already gone through all galleries!') return None fetch_instance.galleries = galleries self.notification_bar.begin_show() fetch_instance.moveToThread(thread) def done(status): self.notification_bar.end_show() fetch_instance.deleteLater() if not isinstance(status, bool): galleries = [] for tup in status: galleries.append(tup[0]) class GalleryContextMenu(QMenu): app_instance = self def __init__(self, parent=None): super().__init__(parent) show_in_library_act = self.addAction('Show in library') show_in_library_act.triggered.connect(self.show_in_library) def show_in_library(self): viewer = self.app_instance.manga_list_view index = viewer.find_index(self.gallery_widget.gallery.id, True) if index: self.app_instance.manga_table_view.scroll_to_index(index) self.app_instance.manga_list_view.scroll_to_index(index) g_popup = io_misc.GalleryPopup(('Fecthing metadata for these galleries failed.'+ ' Check happypanda.log for details.', galleries), self, menu=GalleryContextMenu()) #errors = {g[0].id: g[1] for g in status} #for g_item in g_popup.get_all_items(): # g_item.setToolTip(errors[g_item.gallery.id]) g_popup.graphics_blur.setEnabled(False) close_button = g_popup.add_buttons('Close')[0] close_button.clicked.connect(g_popup.close) fetch_instance.GALLERY_PICKER.connect(self._web_metadata_picker) fetch_instance.GALLERY_EMITTER.connect(self.manga_list_view.replace_edit_gallery) fetch_instance.AUTO_METADATA_PROGRESS.connect(self.notification_bar.add_text) thread.started.connect(fetch_instance.auto_web_metadata) fetch_instance.FINISHED.connect(done) fetch_instance.FINISHED.connect(metadata_spinner.close) fetch_instance.FINISHED.connect(lambda: self.move_listener.disconnect(move_md_spinner)) thread.finished.connect(thread.deleteLater) thread.start() metadata_spinner.show() def init_stat_bar(self): self.status_bar = self.statusBar() self.status_bar.setMaximumHeight(20) self.status_bar.setSizeGripEnabled(False) self.stat_info = QLabel() self.stat_info.setIndent(5) self.sort_main = QAction("Asc", self) sort_menu = QMenu() self.sort_main.setMenu(sort_menu) s_by_title = QAction("Title", sort_menu) s_by_artist = QAction("Artist", sort_menu) sort_menu.addAction(s_by_title) sort_menu.addAction(s_by_artist) self.status_bar.addPermanentWidget(self.stat_info) #self.status_bar.addAction(self.sort_main) self.temp_msg = QLabel() self.temp_timer = QTimer() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.connect(self.stat_row_info) self.manga_list_view.gallery_model.db_emitter.COUNT_CHANGE.connect(self.stat_row_info) self.manga_list_view.gallery_model.STATUSBAR_MSG.connect(self.stat_temp_msg) self.manga_list_view.STATUS_BAR_MSG.connect(self.stat_temp_msg) self.manga_table_view.STATUS_BAR_MSG.connect(self.stat_temp_msg) self.stat_row_info() app_constants.STAT_MSG_METHOD = self.stat_temp_msg def stat_temp_msg(self, msg): self.temp_timer.stop() self.temp_msg.setText(msg) self.status_bar.addWidget(self.temp_msg) self.temp_timer.timeout.connect(self.temp_msg.clear) self.temp_timer.setSingleShot(True) self.temp_timer.start(5000) def stat_row_info(self): r = self.manga_list_view.model().rowCount() t = self.manga_list_view.gallery_model.db_emitter.count self.stat_info.setText("Loaded {} of {} ".format(r, t)) def manga_display(self): "initiates the manga view and related things" #list view self.manga_list_view = gallery.MangaView(self) #table view self.manga_table_view = gallery.MangaTableView(self) self.manga_table_view.gallery_model = self.manga_list_view.gallery_model self.manga_table_view.sort_model = self.manga_list_view.sort_model self.manga_table_view.setModel(self.manga_table_view.sort_model) self.manga_table_view.sort_model.change_model(self.manga_table_view.gallery_model) self.manga_table_view.setColumnWidth(app_constants.FAV, 20) self.manga_table_view.setColumnWidth(app_constants.ARTIST, 200) self.manga_table_view.setColumnWidth(app_constants.TITLE, 400) self.manga_table_view.setColumnWidth(app_constants.TAGS, 300) self.manga_table_view.setColumnWidth(app_constants.TYPE, 60) self.manga_table_view.setColumnWidth(app_constants.CHAPTERS, 60) self.manga_table_view.setColumnWidth(app_constants.LANGUAGE, 100) self.manga_table_view.setColumnWidth(app_constants.LINK, 400) def init_spinners(self): # fetching spinner self.data_fetch_spinner = misc.Spinner(self) self.data_fetch_spinner.set_size(60) self.move_listener.connect( lambda: self.data_fetch_spinner.update_move( QPoint(self.pos().x()+self.width()//2, self.pos().y()+self.height()//2))) self.manga_list_view.gallery_model.ADD_MORE.connect(self.data_fetch_spinner.show) self.manga_list_view.gallery_model.db_emitter.START.connect(self.data_fetch_spinner.show) self.manga_list_view.gallery_model.ADDED_ROWS.connect(self.data_fetch_spinner.before_hide) self.manga_list_view.gallery_model.db_emitter.CANNOT_FETCH_MORE.connect(self.data_fetch_spinner.before_hide) ## deleting spinner #self.gallery_delete_spinner = misc.Spinner(self) #self.gallery_delete_spinner.set_size(40,40) ##self.gallery_delete_spinner.set_text('Removing...') #self.manga_list_view.gallery_model.rowsAboutToBeRemoved.connect(self.gallery_delete_spinner.show) #self.manga_list_view.gallery_model.rowsRemoved.connect(self.gallery_delete_spinner.before_hide) def search(self, srch_string): self.search_bar.setText(srch_string) self.search_backward.setVisible(True) self.manga_list_view.sort_model.init_search(srch_string) old_cursor_pos = self._search_cursor_pos[0] self.search_bar.end(False) if self.search_bar.cursorPosition() != old_cursor_pos+1: self.search_bar.setCursorPosition(old_cursor_pos) def favourite_display(self): "Switches to favourite display" self.manga_table_view.sort_model.fav_view() self.favourite_btn.selected = True self.library_btn.selected = False def catalog_display(self): "Switches to catalog display" self.manga_table_view.sort_model.catalog_view() self.library_btn.selected = True self.favourite_btn.selected = False def settings(self): sett = settingsdialog.SettingsDialog(self) sett.scroll_speed_changed.connect(self.manga_list_view.updateGeometries) #sett.show() def init_toolbar(self): self.toolbar = QToolBar() self.toolbar.setFixedHeight(25) self.toolbar.setWindowTitle("Show") # text for the contextmenu #self.toolbar.setStyleSheet("QToolBar {border:0px}") # make it user defined? self.toolbar.setMovable(False) self.toolbar.setFloatable(False) #self.toolbar.setIconSize(QSize(20,20)) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toolbar.setIconSize(QSize(20,20)) spacer_start = QWidget() # aligns the first actions properly spacer_start.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_start) self.favourite_btn = misc.ToolbarButton(self.toolbar, 'Favorites') self.toolbar.addWidget(self.favourite_btn) self.favourite_btn.clicked.connect(self.favourite_display) #need lambda to pass extra args self.library_btn = misc.ToolbarButton(self.toolbar, 'Library') self.toolbar.addWidget(self.library_btn) self.library_btn.clicked.connect(self.catalog_display) #need lambda to pass extra args self.library_btn.selected = True self.toolbar.addSeparator() gallery_menu = QMenu() gallery_action = QToolButton() gallery_action.setText('Gallery ') gallery_action.setPopupMode(QToolButton.InstantPopup) gallery_action.setToolTip('Contains various gallery related features') gallery_action.setMenu(gallery_menu) add_gallery_icon = QIcon(app_constants.PLUS_PATH) gallery_action_add = QAction(add_gallery_icon, "Add single gallery...", self) gallery_action_add.triggered.connect(self.manga_list_view.SERIES_DIALOG.emit) gallery_action_add.setToolTip('Add a single gallery thoroughly') gallery_menu.addAction(gallery_action_add) add_more_action = QAction(add_gallery_icon, "Add galleries...", self) add_more_action.setStatusTip('Add galleries from different folders') add_more_action.triggered.connect(lambda: self.populate(True)) gallery_menu.addAction(add_more_action) populate_action = QAction(add_gallery_icon, "Populate from directory/archive...", self) populate_action.setStatusTip('Populates the DB with galleries from a single folder or archive') populate_action.triggered.connect(self.populate) gallery_menu.addAction(populate_action) gallery_menu.addSeparator() metadata_action = QAction('Get metadata for all galleries', self) metadata_action.triggered.connect(self.get_metadata) gallery_menu.addAction(metadata_action) scan_galleries_action = QAction('Scan for new galleries', self) scan_galleries_action.triggered.connect(self.scan_for_new_galleries) scan_galleries_action.setStatusTip('Scan monitored folders for new galleries') gallery_menu.addAction(scan_galleries_action) gallery_action_random = gallery_menu.addAction("Open random gallery") gallery_action_random.triggered.connect(self.manga_list_view.open_random_gallery) self.toolbar.addWidget(gallery_action) misc_action = QToolButton() misc_action.setText('Tools ') misc_action_menu = QMenu() misc_action.setMenu(misc_action_menu) misc_action.setPopupMode(QToolButton.InstantPopup) misc_action.setToolTip("Contains misc. features") gallery_downloader = QAction("Gallery Downloader", misc_action_menu) gallery_downloader.triggered.connect(self.download_window.show) misc_action_menu.addAction(gallery_downloader) duplicate_check_simple = QAction("Simple Duplicate Finder", misc_action_menu) duplicate_check_simple.triggered.connect(lambda: self.manga_list_view.duplicate_check()) misc_action_menu.addAction(duplicate_check_simple) self.toolbar.addWidget(misc_action) spacer_middle = QWidget() # aligns buttons to the right spacer_middle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer_middle) sort_action = QToolButton() sort_action.setIcon(QIcon(app_constants.SORT_PATH)) sort_action.setMenu(misc.SortMenu(self.toolbar, self.manga_list_view)) sort_action.setPopupMode(QToolButton.InstantPopup) self.toolbar.addWidget(sort_action) self.grid_toggle_g_icon = QIcon(app_constants.GRID_PATH) self.grid_toggle_l_icon = QIcon(app_constants.LIST_PATH) self.grid_toggle = QToolButton() if self.display.currentIndex() == self.m_l_view_index: self.grid_toggle.setIcon(self.grid_toggle_l_icon) else: self.grid_toggle.setIcon(self.grid_toggle_g_icon) self.grid_toggle.setObjectName('gridtoggle') self.grid_toggle.clicked.connect(self.toggle_view) self.toolbar.addWidget(self.grid_toggle) spacer_mid2 = QWidget() spacer_mid2.setFixedSize(QSize(5, 1)) self.toolbar.addWidget(spacer_mid2) def set_search_case(b): app_constants.GALLERY_SEARCH_CASE = b settings.set(b, 'Application', 'gallery search case') settings.save() def set_search_strict(b): app_constants.GALLERY_SEARCH_STRICT = b settings.set(b, 'Application', 'gallery search strict') settings.save() self.search_bar = misc.LineEdit() search_options = self.search_bar.addAction(QIcon(app_constants.SEARCH_OPTIONS_PATH), QLineEdit.TrailingPosition) search_options_menu = QMenu(self) search_options.triggered.connect(lambda: search_options_menu.popup(QCursor.pos())) search_options.setMenu(search_options_menu) case_search_option = search_options_menu.addAction('Case Sensitive') case_search_option.setCheckable(True) case_search_option.setChecked(app_constants.GALLERY_SEARCH_CASE) case_search_option.toggled.connect(set_search_case) strict_search_option = search_options_menu.addAction('Match whole terms') strict_search_option.setCheckable(True) strict_search_option.setChecked(app_constants.GALLERY_SEARCH_STRICT) strict_search_option.toggled.connect(set_search_strict) self.search_bar.setObjectName('search_bar') self.search_timer = QTimer(self) self.search_timer.setSingleShot(True) self.search_timer.timeout.connect(lambda: self.search(self.search_bar.text())) self._search_cursor_pos = [0, 0] def set_cursor_pos(old, new): self._search_cursor_pos[0] = old self._search_cursor_pos[1] = new self.search_bar.cursorPositionChanged.connect(set_cursor_pos) if app_constants.SEARCH_AUTOCOMPLETE: completer = QCompleter(self) completer_view = misc.CompleterPopupView() completer.setPopup(completer_view) completer_view._setup() completer.setModel(self.manga_list_view.gallery_model) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setCompletionMode(QCompleter.PopupCompletion) completer.setCompletionRole(Qt.DisplayRole) completer.setCompletionColumn(app_constants.TITLE) completer.setFilterMode(Qt.MatchContains) self.search_bar.setCompleter(completer) self.search_bar.returnPressed.connect(lambda: self.search(self.search_bar.text())) if not app_constants.SEARCH_ON_ENTER: self.search_bar.textEdited.connect(lambda: self.search_timer.start(800)) self.search_bar.setPlaceholderText("Search title, artist, namespace & tags") self.search_bar.setMinimumWidth(150) self.search_bar.setMaximumWidth(500) self.search_bar.setFixedHeight(19) self.manga_list_view.sort_model.HISTORY_SEARCH_TERM.connect(lambda a: self.search_bar.setText(a)) self.toolbar.addWidget(self.search_bar) def search_history(_, back=True): # clicked signal passes a bool sort_model = self.manga_list_view.sort_model nav = sort_model.PREV if back else sort_model.NEXT history_term = sort_model.navigate_history(nav) if back: self.search_forward.setVisible(True) back = QShortcut(QKeySequence(QKeySequence.Back), self, lambda: search_history(None)) forward = QShortcut(QKeySequence(QKeySequence.Forward), self, lambda: search_history(None, False)) search_backbutton = QToolButton(self.toolbar) search_backbutton.setText(u'\u25C0') search_backbutton.setFixedWidth(15) search_backbutton.clicked.connect(search_history) self.search_backward = self.toolbar.addWidget(search_backbutton) self.search_backward.setVisible(False) search_forwardbutton = QToolButton(self.toolbar) search_forwardbutton.setText(u'\u25B6') search_forwardbutton.setFixedWidth(15) search_forwardbutton.clicked.connect(lambda: search_history(None, False)) self.search_forward = self.toolbar.addWidget(search_forwardbutton) self.search_forward.setVisible(False) spacer_end = QWidget() # aligns settings action properly spacer_end.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_end) settings_act = QToolButton(self.toolbar) settings_act.setIcon(QIcon(app_constants.SETTINGS_PATH)) settings_act.clicked.connect(self.settings) self.toolbar.addWidget(settings_act) spacer_end2 = QWidget() # aligns About action properly spacer_end2.setFixedSize(QSize(5, 1)) self.toolbar.addWidget(spacer_end2) self.addToolBar(self.toolbar) def toggle_view(self): """ Toggles the current display view """ if self.display.currentIndex() == self.m_l_view_index: self.display.setCurrentIndex(self.m_t_view_index) self.grid_toggle.setIcon(self.grid_toggle_g_icon) else: self.display.setCurrentIndex(self.m_l_view_index) self.grid_toggle.setIcon(self.grid_toggle_l_icon) # TODO: Improve this so that it adds to the gallery dialog, # so user can edit data before inserting (make it a choice) def populate(self, mixed=None): "Populates the database with gallery from local drive'" if mixed: gallery_view = misc.GalleryListView(self, True) gallery_view.SERIES.connect(self.gallery_populate) gallery_view.show() else: msg_box = misc.BasePopup(self) l = QVBoxLayout() msg_box.main_widget.setLayout(l) l.addWidget(QLabel('Directory or Archive?')) l.addLayout(msg_box.buttons_layout) def from_dir(): path = QFileDialog.getExistingDirectory(self, "Choose a directory containing your galleries") if not path: return msg_box.close() app_constants.OVERRIDE_SUBFOLDER_AS_GALLERY = True self.gallery_populate(path, True) def from_arch(): path = QFileDialog.getOpenFileName(self, 'Choose an archive containing your galleries', filter=utils.FILE_FILTER) path = [path[0]] if not all(path) or not path: return msg_box.close() app_constants.OVERRIDE_SUBFOLDER_AS_GALLERY = True self.gallery_populate(path, True) buttons = msg_box.add_buttons('Directory', 'Archive', 'Close') buttons[2].clicked.connect(msg_box.close) buttons[0].clicked.connect(from_dir) buttons[1].clicked.connect(from_arch) msg_box.adjustSize() msg_box.show() def gallery_populate(self, path, validate=False): "Scans the given path for gallery to add into the DB" if len(path) is not 0: data_thread = QThread(self) data_thread.setObjectName('General gallery populate') loading = misc.Loading(self) self.g_populate_inst = fetch.Fetch() self.g_populate_inst.series_path = path loading.show() def finished(status): def hide_loading(): loading.hide() if status: if len(status) != 0: def add_gallery(gallery_list): def append_to_model(x): self.manga_list_view.sort_model.insertRows(x, None, len(x)) self.manga_list_view.sort_model.init_search( self.manga_list_view.sort_model.current_term) class A(QObject): done = pyqtSignal() prog = pyqtSignal(int) def __init__(self, obj, parent=None): super().__init__(parent) self.obj = obj self.galleries = [] def add_to_db(self): for y, x in enumerate(self.obj): gallerydb.add_method_queue( gallerydb.GalleryDB.add_gallery_return, False, x) self.galleries.append(x) y += 1 self.prog.emit(y) append_to_model(self.galleries) self.done.emit() loading.progress.setMaximum(len(gallery_list)) self.a_instance = A(gallery_list) thread = QThread(self) thread.setObjectName('Database populate') def loading_show(numb): if loading.isHidden(): loading.show() loading.setText('Populating database ({}/{})\nPlease wait...'.format( numb, loading.progress.maximum())) loading.progress.setValue(numb) loading.show() def loading_hide(): loading.close() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit() self.a_instance.moveToThread(thread) self.a_instance.prog.connect(loading_show) thread.started.connect(self.a_instance.add_to_db) self.a_instance.done.connect(loading_hide) self.a_instance.done.connect(self.a_instance.deleteLater) #a_instance.add_to_db() thread.finished.connect(thread.deleteLater) thread.start() #data_thread.quit hide_loading() log_i('Populating DB from gallery folder: OK') if validate: gallery_list = misc.GalleryListView(self) gallery_list.SERIES.connect(add_gallery) for ser in status: if ser.is_archive and app_constants.SUBFOLDER_AS_GALLERY: p = os.path.split(ser.path)[1] if ser.chapters[0].path: pt_in_arch = os.path.split(ser.path_in_archive) pt_in_arch = pt_in_arch[1] or pt_in_arch[0] text = '{}: {}'.format(p, pt_in_arch) else: text = p gallery_list.add_gallery(ser, text) else: gallery_list.add_gallery(ser, os.path.split(ser.path)[1]) #self.manga_list_view.gallery_model.populate_data() gallery_list.update_count() gallery_list.show() else: add_gallery(status) else: log_d('No new gallery was found') loading.setText("No new gallery found") #data_thread.quit else: log_e('Populating DB from gallery folder: Nothing was added!') loading.setText("<font color=red>Nothing was added. Check happypanda_log for details..</font>") loading.progress.setStyleSheet("background-color:red;") data_thread.quit QTimer.singleShot(8000, loading.close) def skipped_gs(s_list): "Skipped galleries" msg_box = QMessageBox(self) msg_box.setIcon(QMessageBox.Question) msg_box.setText('Do you want to view skipped paths?') msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box.setDefaultButton(QMessageBox.No) if msg_box.exec() == QMessageBox.Yes: list_wid = QTableWidget(self) list_wid.setAttribute(Qt.WA_DeleteOnClose) list_wid.setRowCount(len(s_list)) list_wid.setColumnCount(2) list_wid.setAlternatingRowColors(True) list_wid.setEditTriggers(list_wid.NoEditTriggers) list_wid.setHorizontalHeaderLabels(['Reason', 'Path']) list_wid.setSelectionBehavior(list_wid.SelectRows) list_wid.setSelectionMode(list_wid.SingleSelection) list_wid.setSortingEnabled(True) list_wid.verticalHeader().hide() list_wid.setAutoScroll(False) for x, g in enumerate(s_list): list_wid.setItem(x, 0, QTableWidgetItem(g[1])) list_wid.setItem(x, 1, QTableWidgetItem(g[0])) list_wid.resizeColumnsToContents() list_wid.setWindowTitle('{} skipped paths'.format(len(s_list))) list_wid.setWindowFlags(Qt.Window) list_wid.resize(900,400) list_wid.doubleClicked.connect(lambda i: utils.open_path( list_wid.item(i.row(), 1).text(), list_wid.item(i.row(), 1).text())) list_wid.show() def a_progress(prog): loading.progress.setValue(prog) loading.setText("Preparing galleries...") self.g_populate_inst.moveToThread(data_thread) self.g_populate_inst.DATA_COUNT.connect(loading.progress.setMaximum) self.g_populate_inst.PROGRESS.connect(a_progress) self.g_populate_inst.FINISHED.connect(finished) self.g_populate_inst.FINISHED.connect(self.g_populate_inst.deleteLater) self.g_populate_inst.SKIPPED.connect(skipped_gs) data_thread.finished.connect(data_thread.deleteLater) data_thread.started.connect(self.g_populate_inst.local) data_thread.start() #.g_populate_inst.local() log_i('Populating DB from directory/archive') def scan_for_new_galleries(self): available_folders = app_constants.ENABLE_MONITOR and\ app_constants.MONITOR_PATHS and all(app_constants.MONITOR_PATHS) if available_folders and not app_constants.SCANNING_FOR_GALLERIES: app_constants.SCANNING_FOR_GALLERIES = True self.notification_bar.add_text("Scanning for new galleries...") log_i('Scanning for new galleries...') try: class ScanDir(QObject): final_paths_and_galleries = pyqtSignal(list, list) finished = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.scanned_data = [] def scan_dirs(self): paths = [] for p in app_constants.MONITOR_PATHS: if os.path.exists(p): dir_content = scandir.scandir(p) for d in dir_content: paths.append(d.path) else: log_e("Monitored path does not exists: {}".format(p.encode(errors='ignore'))) fetch_inst = fetch.Fetch(self) fetch_inst.series_path = paths def set_scanned_d(d): self.scanned_data = d fetch_inst.FINISHED.connect(set_scanned_d) fetch_inst.local() #contents = [] #for g in self.scanned_data: # contents.append(g) #paths = sorted(paths) #new_galleries = [] #for x in contents: # y = utils.b_search(paths, os.path.normcase(x.path)) # if not y: # new_galleries.append(x) galleries = [] final_paths = [] if self.scanned_data: for g in self.scanned_data: try: if g.is_archive: g.profile = utils.get_gallery_img(g.chapters[0].path, g.path) else: g.profile = utils.get_gallery_img(g.chapters[0].path) if not g.profile: raise Exception except: g.profile = app_constants.NO_IMAGE_PATH galleries.append(g) final_paths.append(g.path) self.final_paths_and_galleries.emit(final_paths, galleries) self.finished.emit() self.deleteLater() #if app_constants.LOOK_NEW_GALLERY_AUTOADD: # QTimer.singleShot(10000, self.gallery_populate(final_paths)) # return def show_new_galleries(final_paths, galleries): if final_paths and galleries: app_constants.OVERRIDE_MOVE_IMPORTED_IN_FETCH = True if app_constants.LOOK_NEW_GALLERY_AUTOADD: self.gallery_populate(final_paths) else: class NewGalleryMenu(QMenu): def __init__(self, parent=None): super().__init__(parent) ignore_act = self.addAction('Add to ignore list') ignore_act.triggered.connect(self.add_to_ignore) def add_to_ignore(self): gallery = self.gallery_widget.gallery app_constants.IGNORE_PATHS.append(gallery.path) settings.set(app_constants.IGNORE_PATHS, 'Application', 'ignore paths') if self.gallery_widget.parent_widget.gallery_layout.count() == 1: self.gallery_widget.parent_widget.close() else: self.gallery_widget.close() if len(galleries) == 1: self.notification_bar.add_text("{} new gallery was discovered in one of your monitored directories".format(len(galleries))) else: self.notification_bar.add_text("{} new galleries were discovered in one of your monitored directories".format(len(galleries))) text = "These new galleries were discovered! Do you want to add them?"\ if len(galleries) > 1 else "This new gallery was discovered! Do you want to add it?" g_popup = io_misc.GalleryPopup((text, galleries), self, NewGalleryMenu()) buttons = g_popup.add_buttons('Add', 'Close') def populate_n_close(): g_popup.close() self.gallery_populate(final_paths) buttons[0].clicked.connect(populate_n_close) buttons[1].clicked.connect(g_popup.close) def finished(): app_constants.SCANNING_FOR_GALLERIES = False thread = QThread(self) self.scan_inst = ScanDir() self.scan_inst.moveToThread(thread) self.scan_inst.final_paths_and_galleries.connect(show_new_galleries) self.scan_inst.finished.connect(finished) thread.started.connect(self.scan_inst.scan_dirs) #self.scan_inst.scan_dirs() thread.finished.connect(thread.deleteLater) thread.start() except: self.notification_bar.add_text('An error occured while attempting to scan for new galleries. Check happypanda.log.') log.exception('An error occured while attempting to scan for new galleries.') app_constants.SCANNING_FOR_GALLERIES = False def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.acceptProposedAction() else: self.notification_bar.add_text('File is not supported') def dropEvent(self, event): acceptable = [] unaccept = [] for u in event.mimeData().urls(): path = u.toLocalFile() if os.path.isdir(path) or path.endswith(utils.ARCHIVE_FILES): acceptable.append(path) else: unaccept.append(path) log_i('Acceptable dropped items: {}'.format(len(acceptable))) log_i('Unacceptable dropped items: {}'.format(len(unaccept))) log_d('Dropped items: {}\n{}'.format(acceptable, unaccept).encode(errors='ignore')) if acceptable: self.notification_bar.add_text('Adding dropped items...') log_i('Adding dropped items') l = len(acceptable) == 1 f_item = acceptable[0] if f_item.endswith(utils.ARCHIVE_FILES): f_item = utils.check_archive(f_item) else: f_item = utils.recursive_gallery_check(f_item) f_item_l = len(f_item) < 2 subfolder_as_c = not app_constants.SUBFOLDER_AS_GALLERY if l and subfolder_as_c or l and f_item_l: g_d = gallerydialog.GalleryDialog(self, acceptable[0]) g_d.SERIES.connect(self.manga_list_view.gallery_model.addRows) g_d.show() else: self.gallery_populate(acceptable, True) else: text = 'File not supported' if len(unaccept) < 2 else 'Files not supported' self.notification_bar.add_text(text) if unaccept: self.notification_bar.add_text('Some unsupported files did not get added') def resizeEvent(self, event): try: self.notification_bar.resize(event.size().width()) except AttributeError: pass self.move_listener.emit() return super().resizeEvent(event) def moveEvent(self, event): self.move_listener.emit() return super().moveEvent(event) def showEvent(self, event): return super().showEvent(event) def cleanup_exit(self): self.system_tray.hide() # watchers try: self.watchers.stop_all() except AttributeError: pass # settings settings.set(self.manga_list_view.current_sort, 'General', 'current sort') settings.set(app_constants.IGNORE_PATHS, 'Application', 'ignore paths') settings.win_save(self, 'AppWindow') # temp dir try: for root, dirs, files in scandir.walk('temp', topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) log_d('Flush temp on exit: OK') except: log.exception('Flush temp on exit: FAIL') if self.tags_treeview: self.tags_treeview.close() self.download_window.close() # check if there is db activity if not gallerydb.method_queue.empty(): class DBActivityChecker(QObject): FINISHED = pyqtSignal() def __init__(self, **kwargs): super().__init__(**kwargs) def check(self): gallerydb.method_queue.join() self.FINISHED.emit() self.deleteLater() db_activity = DBActivityChecker() db_spinner = misc.Spinner(self) self.db_activity_checker.connect(db_activity.check) db_activity.moveToThread(app_constants.GENERAL_THREAD) db_activity.FINISHED.connect(db_spinner.close) db_spinner.set_size(50) db_spinner.set_text('Activity') db_spinner.move(QPoint(self.pos().x()+self.width()-70, self.pos().y()+self.height()-70)) self.move_listener.connect(lambda: db_spinner.update_move(QPoint(self.pos().x()+self.width()-70, self.pos().y()+self.height()-70))) db_spinner.show() self.db_activity_checker.emit() msg_box = QMessageBox(self) msg_box.setText('Database activity detected!') msg_box.setInformativeText("Closing now might result in data loss." + " Do you still want to close?\n(Wait for the activity spinner to hide before closing)") msg_box.setIcon(QMessageBox.Critical) msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box.setDefaultButton(QMessageBox.No) if msg_box.exec() == QMessageBox.Yes: return 1 else: return 2 else: return 0 def closeEvent(self, event): r_code = self.cleanup_exit() if r_code == 1: log_d('Force Exit App: OK') super().closeEvent(event) elif r_code == 2: log_d('Ignore Exit App') event.ignore() else: log_d('Normal Exit App: OK') super().closeEvent(event)
class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.resize(800, 600) MainWindow.setMinimumSize(600, 400) icon = QIcon() icon.addPixmap(QPixmap("icon.png"), QIcon.Normal, QIcon.Off) font = QFont() font.setFamily("MS Sans Serif") font.setBold(True) font.setWeight(75) MainWindow.setWindowIcon(icon) self.centralwidget = QWidget(MainWindow) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(MainWindow) self.menuFiles = QMenu(self.menubar) self.menuMark = QMenu(self.menubar) self.menuCommands = QMenu(self.menubar) self.menuNet = QMenu(self.menubar) self.menuShow = QMenu(self.menubar) self.menuConfiguration = QMenu(self.menubar) self.menuStart = QMenu(self.menubar) MainWindow.setMenuBar(self.menubar) self.helpMenuBar = QMenuBar(self.menubar) self.menuHelp = QMenu(self.helpMenuBar) self.helpMenuBar.addMenu(self.menuHelp) self.menubar.setCornerWidget(self.helpMenuBar) self.toolBar = QToolBar(MainWindow) self.toolBar.setMovable(False) MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar) self.toolBar_2 = QToolBar(MainWindow) font2 = QFont() font2.setFamily("MS Sans Serif") font2.setWeight(700) font2.setPixelSize(8) font2.setBold(True) self.model1 = QFileSystemModel() self.model1.setRootPath('C:\\Windows') self.tree1 = QTreeView() self.tree1.setModel(self.model1) self.tree1.setRootIndex(self.model1.index("C:\\Windows\\")) self.tree1.setAnimated(False) self.tree1.setFont(font2) self.tree1.setIndentation(20) self.tree1.setSortingEnabled(True) self.tree1.setItemsExpandable(False) self.tree1.setRootIsDecorated(False) self.tree1.sortByColumn(0, QtCore.Qt.AscendingOrder) self.tree1.resize(350, 480) self.model2 = QFileSystemModel() self.model2.setRootPath('C:\\Windows\\System32') self.tree2 = QTreeView() self.tree2.setModel(self.model2) self.tree2.setRootIndex(self.model2.index("C:\\Windows\\System32\\")) self.tree2.setAnimated(False) self.tree2.setFont(font2) self.tree2.setIndentation(20) self.tree2.setItemsExpandable(False) self.tree2.setRootIsDecorated(False) self.tree2.setSortingEnabled(True) self.tree2.resize(350, 480) for i in range(1, self.tree1.model().columnCount()): self.tree1.header().hideSection(i) for i in range(1, self.tree2.model().columnCount()): self.tree2.header().hideSection(i) self.centralHBox = QHBoxLayout() self.centralHBox.addWidget(self.tree1) self.centralHBox.addWidget(self.tree2) self.centralHBox.setContentsMargins(3, 3, 3, 3) self.centralwidget.setLayout(self.centralHBox) self.toolBar_2.setMovable(False) self.toolBar_3 = QToolBar(MainWindow) self.toolBar_3.setFixedHeight(30) self.toolBar_3.setContentsMargins(1, 1, 1, 1) self.dirLabel = QLabel() self.dirLabel.setText('c:\\Windows>') self.dirBox = QComboBox() self.dirBox.setEditable(True) self.dirBox.addItem('') self.dirBox.setFixedWidth(470) self.spacer = QWidget() self.spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.toolBar_3.addWidget(self.spacer) self.toolBar_3.addWidget(self.dirLabel) self.toolBar_3.addWidget(self.dirBox) self.toolBar_3.setFont(font) self.toolBar_3.setMovable(False) self.toolBar_3.setStyleSheet("QToolBar{spacing: 5px;}") MainWindow.addToolBar(QtCore.Qt.BottomToolBarArea, self.toolBar_2) MainWindow.addToolBar(QtCore.Qt.BottomToolBarArea, self.toolBar_3) MainWindow.insertToolBarBreak(self.toolBar_3) self.actionIndex = QAction(MainWindow) icon1 = QIcon() icon1.addPixmap(QPixmap("help.png"), QIcon.Normal, QIcon.Off) self.actionIndex.setIcon(icon1) self.actionKeyboard = QAction(MainWindow) self.actionRegistration_Info = QAction(MainWindow) self.actionVisit_Total_CMD_s_website = QAction(MainWindow) self.actionAbout_Total_Commander = QAction(MainWindow) self.actionOption_1 = QAction(MainWindow) self.actionOption_2 = QAction(MainWindow) self.actionOption_3 = QAction(MainWindow) self.actionOption_4 = QAction(MainWindow) self.actionOption_5 = QAction(MainWindow) self.actionOption_6 = QAction(MainWindow) self.actionOption_7 = QAction(MainWindow) self.actionOption_8 = QAction(MainWindow) self.actionOption_9 = QAction(MainWindow) self.actionOption_10 = QAction(MainWindow) self.actionOption_11 = QAction(MainWindow) self.actionOption_12 = QAction(MainWindow) self.actionOption_13 = QAction(MainWindow) self.actionOption_14 = QAction(MainWindow) self.Refresh = QAction(MainWindow) icon2 = QIcon() icon2.addPixmap(QPixmap("refresh.png"), QIcon.Normal, QIcon.Off) self.Refresh.setIcon(icon2) self.action_view1 = QAction(MainWindow) icon3 = QIcon() icon3.addPixmap(QPixmap("folder_structure_1.png"), QIcon.Normal, QIcon.Off) self.view_group = QActionGroup(MainWindow) self.action_view1.setIcon(icon3) self.action_view1.setCheckable(True) self.action_view2 = QAction(MainWindow) self.action_view2.setCheckable(True) self.action_view2.setChecked(True) icon4 = QIcon() icon4.addPixmap(QPixmap("folder_structure_2.png"), QIcon.Normal, QIcon.Off) self.action_view2.setIcon(icon4) self.view_group.addAction(self.action_view1) self.view_group.addAction(self.action_view2) self.view_group.setExclusive(True) self.actionF3_View = QToolButton(MainWindow) self.actionF3_View.setStyleSheet("QToolButton{border: none;}") self.actionF3_View.setFont(font) self.actionF4_Edit = QToolButton(MainWindow) self.actionF4_Edit.setStyleSheet("QToolButton{border: none;}") self.actionF4_Edit.setFont(font) self.actionF5_Copy = QToolButton(MainWindow) self.actionF5_Copy.setStyleSheet("QToolButton{border: none;}") self.actionF5_Copy.setFont(font) self.actionF6_Move = QToolButton(MainWindow) self.actionF6_Move.setStyleSheet("QToolButton{border: none;}") self.actionF6_Move.setFont(font) self.actionF5_NewFolder = QToolButton(MainWindow) self.actionF5_NewFolder.setStyleSheet("QToolButton{border: none;}") self.actionF5_NewFolder.setFont(font) self.actionF8_Delete = QToolButton(MainWindow) self.actionF8_Delete.setStyleSheet("QToolButton{border: none;}") self.actionF8_Delete.setFont(font) self.actionAlt_F4_Exit = QToolButton(MainWindow) self.actionAlt_F4_Exit.setStyleSheet("QToolButton{border: none;}") self.actionAlt_F4_Exit.setFont(font) self.menuFiles.addAction(self.actionOption_1) self.menuFiles.addAction(self.actionOption_2) self.menuMark.addAction(self.actionOption_3) self.menuMark.addAction(self.actionOption_4) self.menuCommands.addAction(self.actionOption_5) self.menuCommands.addAction(self.actionOption_6) self.menuNet.addAction(self.actionOption_7) self.menuNet.addAction(self.actionOption_8) self.menuShow.addAction(self.actionOption_9) self.menuShow.addAction(self.actionOption_10) self.menuConfiguration.addAction(self.actionOption_11) self.menuConfiguration.addAction(self.actionOption_12) self.menuStart.addAction(self.actionOption_13) self.menuStart.addAction(self.actionOption_14) self.menuHelp.addAction(self.actionIndex) self.menuHelp.addAction(self.actionKeyboard) self.menuHelp.addAction(self.actionRegistration_Info) self.menuHelp.addAction(self.actionVisit_Total_CMD_s_website) self.menuHelp.addSeparator() self.menuHelp.addAction(self.actionAbout_Total_Commander) self.menubar.addAction(self.menuFiles.menuAction()) self.menubar.addAction(self.menuMark.menuAction()) self.menubar.addAction(self.menuCommands.menuAction()) self.menubar.addAction(self.menuNet.menuAction()) self.menubar.addAction(self.menuShow.menuAction()) self.menubar.addAction(self.menuConfiguration.menuAction()) self.menubar.addAction(self.menuStart.menuAction()) self.helpMenuBar.addAction(self.menuHelp.menuAction()) self.toolBar.addAction(self.Refresh) self.toolBar.addSeparator() self.toolBar.addAction(self.action_view1) self.toolBar.addSeparator() self.toolBar.addAction(self.action_view2) self.separator1 = QFrame() self.separator1.setFrameShape(QFrame.VLine) self.separator1.setFrameShadow(QFrame.Sunken) self.separator2 = QFrame() self.separator2.setFrameShape(QFrame.VLine) self.separator2.setFrameShadow(QFrame.Sunken) self.separator3 = QFrame() self.separator3.setFrameShape(QFrame.VLine) self.separator3.setFrameShadow(QFrame.Sunken) self.separator4 = QFrame() self.separator4.setFrameShape(QFrame.VLine) self.separator4.setFrameShadow(QFrame.Sunken) self.separator5 = QFrame() self.separator5.setFrameShape(QFrame.VLine) self.separator5.setFrameShadow(QFrame.Sunken) self.separator6 = QFrame() self.separator6.setFrameShape(QFrame.VLine) self.separator6.setFrameShadow(QFrame.Sunken) self.toolbarHBoxWidget = QWidget() self.toolbarHBoxWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.toolbarHBox = QGridLayout() self.toolbarHBoxWidget.setLayout(self.toolbarHBox) self.toolbarHBox.addWidget(self.actionF3_View, 0, 0) self.toolbarHBox.addWidget(self.separator1, 0, 1) self.toolbarHBox.addWidget(self.actionF4_Edit, 0, 2) self.toolbarHBox.addWidget(self.separator2, 0, 3) self.toolbarHBox.addWidget(self.actionF5_Copy, 0, 4) self.toolbarHBox.addWidget(self.separator3, 0, 5) self.toolbarHBox.addWidget(self.actionF6_Move, 0, 6) self.toolbarHBox.addWidget(self.separator4, 0, 7) self.toolbarHBox.addWidget(self.actionF5_NewFolder, 0, 8) self.toolbarHBox.addWidget(self.separator5, 0, 9) self.toolbarHBox.addWidget(self.actionF8_Delete, 0, 10) self.toolbarHBox.addWidget(self.separator6, 0, 11) self.toolbarHBox.addWidget(self.actionAlt_F4_Exit, 0, 12) self.toolBar_2.addWidget(self.toolbarHBoxWidget) self.toolBar_2.setFixedHeight(40) self.retranslateUi(MainWindow) QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): MainWindow.setWindowTitle( "Total Commander 7.50a - Politechnika Lodzka - Wydzial EEIA") self.menuFiles.setTitle("Files") self.menuMark.setTitle("Mark") self.menuCommands.setTitle("Commands") self.menuNet.setTitle("Net") self.menuShow.setTitle("Show") self.menuConfiguration.setTitle("Configuration") self.menuStart.setTitle("Start") self.menuHelp.setTitle("Help") self.toolBar.setWindowTitle("toolBar") self.toolBar_2.setWindowTitle("toolBar_2") self.actionIndex.setText("Index") self.actionIndex.setIconText("Index") self.actionIndex.setShortcut("F1") self.actionKeyboard.setText("Keyboard") self.actionRegistration_Info.setText("Registration Info") self.actionVisit_Total_CMD_s_website.setText( "Visit Totalcmd\'s Web Site") self.actionAbout_Total_Commander.setText("About Total Commander...") self.actionOption_1.setText("Option 1") self.actionOption_2.setText("Option 2") self.actionOption_3.setText("Option 1") self.actionOption_4.setText("Option 2") self.actionOption_5.setText("Option 1") self.actionOption_6.setText("Option 2") self.actionOption_7.setText("Option 1") self.actionOption_8.setText("Option 2") self.actionOption_9.setText("Option 1") self.actionOption_10.setText("Option 2") self.actionOption_11.setText("Option 1") self.actionOption_12.setText("Option 2") self.actionOption_13.setText("Option 1") self.actionOption_14.setText("Option 2") self.Refresh.setText("Refresh") self.action_view1.setText("View 1") self.action_view2.setText("View 2") self.actionF3_View.setText("F3 View") self.actionF3_View.setShortcut("F3") self.actionF4_Edit.setText("F4 Edit") self.actionF4_Edit.setShortcut("F4") self.actionF5_Copy.setText("F5 Copy") self.actionF5_Copy.setShortcut("F5") self.actionF6_Move.setText("F6 Move") self.actionF6_Move.setShortcut("F6") self.actionF5_NewFolder.setText("F7 NewFolder") self.actionF5_NewFolder.setShortcut("F7") self.actionF8_Delete.setText("F8 Delete") self.actionF8_Delete.setShortcut("F8") self.actionAlt_F4_Exit.setText("Alt+F4 Exit") self.actionAlt_F4_Exit.setShortcut("Ctrl+Alt+F4")
class DMSDecorateTypeWgt(QDialog): DEBUG = True tableWdg = None # 数据列位置 TASK_ID = 0 TASK_ORDER = 1 TASK_NAME = 2 TASK_DURATION = 3 TASK_PRE = 4 TASK_ROOM_BELONG = 5 TASK_RESPONSIBLE = 6 TASK_NODE_X = 7 TASK_NODE_Y = 8 TASK_NODE_COLOR = 9 # 表头 HEADER_NAME = [ "id", '序号', '任务名称', '工期', '前置任务', '房间', '责任人', "x", "y", "颜色" ] TOOL_BAR_HEIGHT = 30 TABLE_HEADER_HEIGHT = 30 def __init__(self, parent=None): super(DMSDecorateTypeWgt, self).__init__(parent) self.tableWdg = QTableWidget(0, len(DMSDecorateTypeWgt.HEADER_NAME)) self.toolBar = QToolBar('toolBar', self) self.actInsertTask = QAction(text="插入") self.actDelTask = QAction(text="删除") self.toolBar.addAction(self.actInsertTask) self.toolBar.addAction(self.actDelTask) self.commitBtn = QPushButton("确定") self.cancelBtn = QPushButton("取消") self.initUI() self.initSignal() pass def initSignal(self): self.actInsertTask.triggered.connect(self.addDecorateType) # 插入任务 self.actDelTask.triggered.connect(self.delDecorateType) # 删除任务 self.commitBtn.clicked.connect(self.submit) # 提交 self.cancelBtn.clicked.connect(self.cancel) # 取消 def initUI(self): '''布局''' self.setWindowTitle("编辑计划模板") self.setWindowModality(Qt.WindowModal) layout = QVBoxLayout(self) layout.addWidget(self.toolBar) layout.addWidget(self.tableWdg) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.resize(1200, 600) self.setFixedWidth(1200) '''底部按钮''' bottomBar = QHBoxLayout() bottomBar.setSpacing(4) bottomBar.setContentsMargins(10, 10, 24, 16) spacerItem = QSpacerItem(20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) bottomBar.addItem(spacerItem) bottomBar.addWidget(self.commitBtn) bottomBar.addWidget(self.cancelBtn) self.commitBtn.setFixedSize(80, glb_dmsContext.BUTTON_NORMAL_HEIGHT) self.cancelBtn.setFixedSize(80, glb_dmsContext.BUTTON_NORMAL_HEIGHT) layout.addLayout(bottomBar) '''工具栏''' self.toolBar.setFixedHeight(DMSDecorateTypeWgt.TOOL_BAR_HEIGHT) '''中部表格''' # 隐藏不必要的列 if not glb_dmsContext.IS_DEBUG: self.tableWdg.hideColumn(DMSDecorateTypeWgt.TASK_ID) self.tableWdg.hideColumn(DMSDecorateTypeWgt.TASK_NODE_X) self.tableWdg.hideColumn(DMSDecorateTypeWgt.TASK_NODE_Y) self.tableWdg.hideColumn(DMSDecorateTypeWgt.TASK_NODE_COLOR) # 尾列可伸缩 self.tableWdg.horizontalHeader().setStretchLastSection(True) # 设置表头 self.tableWdg.horizontalHeader().setFixedHeight( DMSDecorateTypeWgt.TABLE_HEADER_HEIGHT) # 列名 self.tableWdg.setHorizontalHeaderLabels(DMSDecorateTypeWgt.HEADER_NAME) self.tableWdg.verticalHeader().setVisible(False) self.tableWdg.verticalHeader().setFixedWidth(30) '''设置表选择和编辑行为''' self.tableWdg.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.AnyKeyPressed) self.tableWdg.setSelectionMode(QAbstractItemView.ExtendedSelection) # Todo # self.tableWdg.setSelectionBehavior(QAbstractItemView.sel) # self.setWindowFlags(Qt.FramelessWindowHint) self.tableWdg.setShowGrid(False) # self.tableWdg.setStyleSheet("QTableWidget::Item{border:0px solid rgb(255,0,0);border-bottom:1px solid rgb(255,0,0);}") def updateDecorateType(self, currentBuildingAndCurrentUnitID): self.tableWdg.clear() task: DB_Decorate_Type decorateTaskList: List[DB_Decorate_Type] = dmsDatabase().getTableList( table=DB_Decorate_Type, filter_str=currentBuildingAndCurrentUnitID) rowNumber = 0 for task in decorateTaskList: self.tableWdg.setItem(rowNumber, DMSDecorateTypeWgt.TASK_ID, QTableWidgetItem(task.id)) self.tableWdg.setItem(rowNumber, DMSDecorateTypeWgt.TASK_ORDER, QTableWidgetItem(task.order)) self.tableWdg.setItem(rowNumber, DMSDecorateTypeWgt.TASK_NAME, QTableWidgetItem(task.name)) self.tableWdg.setItem(rowNumber, DMSDecorateTypeWgt.TASK_PRE, QTableWidgetItem(task.pre_task)) self.tableWdg.setItem(rowNumber, DMSDecorateTypeWgt.TASK_DURATION, QTableWidgetItem(task.duration)) self.tableWdg.setItem(rowNumber, DMSDecorateTypeWgt.TASK_NODE_COLOR, QTableWidgetItem(task.node_color)) self.tableWdg.setItem(rowNumber, DMSDecorateTypeWgt.TASK_NODE_X, QTableWidgetItem(task.node_x)) self.tableWdg.setItem(rowNumber, DMSDecorateTypeWgt.TASK_NODE_Y, QTableWidgetItem(task.node_y)) rowNumber = rowNumber + 1 def getCurrentItem(self): return self.tableWdg.currentItem() def addDecorateType(self): itemList = self.tableWdg.selectedItems() [itemRow, itemCol] = [ self.tableWdg.rowCount(), self.TASK_ORDER ] if len(itemList) == 0 else [itemList[0].row(), itemList[0].column()] # print("before insert: current", self.tableWdg.currentItem().row(), self.tableWdg.currentItem().column()) self.tableWdg.insertRow(itemRow) self.initNewRow(itemRow) def delDecorateType(self): itemList = self.tableWdg.selectedItems() if len(itemList) == 0: return else: item = itemList[0] taskID = self.tableWdg.item( item.row(), DMSDecorateTypeWgt.TASK_ID).data(Qt.UserRole) self.tableWdg.removeRow(item.row()) def saveDecorateTypeDate2DB(self): pass def checkDateVisable(self): # 数据校验 pass def initNewRow(self, rowNum): self.tableWdg.setItem(rowNum, self.TASK_ID, QTableWidgetItem(str(uuid.uuid1()))) self.tableWdg.setItem(rowNum, self.TASK_NODE_X, QTableWidgetItem("0")) self.tableWdg.setItem(rowNum, self.TASK_NODE_Y, QTableWidgetItem("0")) self.tableWdg.setItem(rowNum, self.TASK_NODE_COLOR, QTableWidgetItem(GNode.brush.color().name())) # 设置色彩控件 self.tableWdg.item(rowNum, self.TASK_NODE_COLOR).setBackground( QBrush(QColor(GNode.brush.color().name()))) self.tableWdg.item(rowNum, self.TASK_NODE_COLOR).setFlags( Qt.ItemIsSelectable | Qt.ItemIsEnabled) # 单元格不可被编辑 # self.tableWdg.item(rowNum, self.TASK_NODE_COLOR). # col = QColorDialog.getColor() # col.name() def currAndSelect(self): print("after insert: current", self.tableWdg.currentItem().row(), self.tableWdg.currentItem().column()) print("after insert: selected", self.tableWdg.selectedItems()[0].row(), self.tableWdg.selectedItems()[0].column()) def submit(self): self.saveDecorateTypeDate2DB() self.close() def cancel(self): # todo 清空session self.close()
class MainWindow(QMainWindow): def __init__(self, app): super().__init__() self.app = app self.setWindowTitle("Automatoes") self.setWindowIcon(QIcon('automato.png')) desktop = app.desktop() height = desktop.screenGeometry().height() width = desktop.screenGeometry().width() self.setGeometry(width / 4 - 100, height / 4 - 100, width / 2, height / 2) self.setFixedSize(width / 2 + 200, height / 2 + 200) self.setup_components() def setup_components(self): self.statusbar_setup() self.central_widget_setup() self.toolbar_widget_setup() def statusbar_setup(self): stat = QStatusBar() self.setStatusBar(stat) label1 = QLabel() label1.setText("Label 1") label1.show() stat.addWidget(label1, 1) def central_widget_setup(self): home = SmartHomeView(self.app.home) self.setCentralWidget(home) def toolbar_widget_setup(self): new_user = QPushButton('Add new user', ) new_user.clicked.connect(self.open_new_user_dialog) self.newAction = QAction(QIcon("Add_user.png"), "Add new user", self, statusTip="Add new user", triggered=self.open_new_user_dialog) self.logoutAction = QAction(QIcon("logout.png"), "Logout", self, statusTip="Add new user", triggered=self.close_action) self.mainToolBar = QToolBar() self.mainToolBar.addAction(self.newAction) self.mainToolBar.addSeparator() self.mainToolBar.addAction(self.logoutAction) self.addToolBar(self.mainToolBar) self.mainToolBar.setFixedHeight(36) self.mainToolBar.setFloatable(False) def open_new_user_dialog(self): dialog = NewUserDialog() dialog.show() dialog.exec_() def close_action(self): self.close()