class AddOperatorWidget(QWidget): def __init__(self, controller: EzCVController, parent=None): super().__init__(parent) self.setWindowFlag(Qt.WindowStaysOnTopHint) self._controller = controller self.operators = { cls.__name__: cls for cls in get_available_operators() } self.available_operators_list = QListWidget(self) self.available_operators_list.doubleClicked.connect( self.accept_operator) self.available_operators_list.installEventFilter(self) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept_operator) self.button_box.rejected.connect(self.hide) self.init_ui() def init_ui(self): self.resize(300, 400) main_layout = QVBoxLayout(self) main_layout.addWidget(self.available_operators_list, stretch=5) main_layout.addWidget(self.button_box, stretch=1) self.available_operators_list.addItems(self.operators.keys()) self.available_operators_list.setCurrentRow(0) def accept_operator(self): selected_item = self.available_operators_list.currentItem().text() selected_operator = self.operators[selected_item] self._controller.add_operator(selected_operator) self.hide() def eventFilter(self, watched, event): """ Catch ENTER and ESC key presses """ if event.type() == QEvent.KeyPress: if event.matches(QKeySequence.InsertParagraphSeparator): self.accept_operator() elif event.matches(QKeySequence.Cancel): self.hide() return False
class HeaderEditDialog(QDialog): header_changed = pyqtSignal(str, list) def __init__(self, parent, columns): super().__init__(parent) self.columns = copy.deepcopy(columns) self.setupUi() def setupUi(self): self.resize(200, 400) self.vbox = QVBoxLayout(self) self.columnList = QListWidget(self) self.vbox.addWidget(self.columnList) self.columnList.setDragDropMode(QListWidget.InternalMove) self.columnList.setDefaultDropAction(Qt.MoveAction) self.columnList.setSelectionMode(QListWidget.ExtendedSelection) self.columnList.setAlternatingRowColors(True) self.columnList.installEventFilter(self) self.columnList.setObjectName("ColumnList") # self.columnList.setContextMenuPolicy(Qt.CustomContextMenu) # self.columnList.customContextMenuRequested.connect(self.open_menu) self.buttonBox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self) self.vbox.addWidget(self.buttonBox) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.fill_column_list() def eventFilter(self, object, event): if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return: self.toggle_selected_columns() return True return False def fill_column_list(self): for column in self.columns: ColumnListItem(self.columnList, column) def accept(self): result = [] for i in range(self.columnList.count()): item = self.columnList.item(i) result.append(item.column) self.header_changed.emit('rearrange', result) self.done(0) def reject(self): self.done(0) def toggle_selected_columns(self): selected = self.columnList.selectedItems() for item in selected: value_now = item.data(Qt.CheckStateRole) item.setData(Qt.CheckStateRole, not value_now) self.columnList.reset( ) # @Improvement: is there a better way to update QListWidget? # def load_preset(self, action): # name = action.text() # self.result_future(('load', name)) # # def save_preset(self, action): # result = [] # for i in range(self.columnList.count()): # column list has to be generated here because if # item = self.columnList.item(i) # you rearrange and save, then what gets saved is # result.append(item.column) # the un-rearranged list from the table header # # name = action.text() # if action.property('new'): # self.result_future(('save new', (name, result))) # else: # self.result_future(('save', (name, result))) # def open_menu(self, position): # return # @TODO: implement header presets # menu = QMenu(self) # # load_menu = QMenu('Load preset', self) # save_menu = QMenu('Save preset as', self) # save_new_action = save_menu.addAction('New') # save_new_action.setProperty('new', True) # save_menu.addSeparator() # # presets = CONFIG.get_columns_presets() # for preset in presets: # load_menu.addAction(preset) # save_menu.addAction(preset) # # load_menu.triggered.connect(self.load_preset) # save_menu.triggered.connect(self.save_preset) # # menu.addMenu(load_menu) # menu.addMenu(save_menu) # # menu.popup(self.columnList.viewport().mapToGlobal(position)) def get_selected_items(self): result = [] selected = self.columnList.selectedIndexes() for index in selected: item = self.columnList.itemFromIndex(index) result.append(item) return result def enable_selected(self): selected = self.get_selected_items() for item in selected: item.setData(Qt.CheckStateRole, Qt.Checked) def disable_selected(self): selected = self.get_selected_items() for item in selected: item.setData(Qt.CheckStateRole, Qt.Unchecked)
class Window(QtWidgets.QWidget): def __init__(self, parent=None): super(Window, self).__init__(parent) self.user_interface() def item_click(self, item): number = self.view.currentRow() if self.shuffled == False: self.playlist.setCurrentIndex(number) self.player.play() elif self.shuffled == True: pass def item_doubleclick(self, item): self.current_item_song = item menu = QtWidgets.QMenu() createPlaylist = QAction('Create New Playlist') createPlaylist.triggered.connect(self.create_playlist) menu.addAction(createPlaylist) addToPlaylist = QAction('Add To Playlist') addToPlaylist.triggered.connect(self.add_to_playlist) menu.addAction(addToPlaylist) menu.exec_() def user_interface(self): self.all_song_button = QtWidgets.QPushButton("All songs") self.play_button = QtWidgets.QPushButton() self.play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) self.next_button = QtWidgets.QPushButton() self.next_button.setIcon(self.style().standardIcon( QStyle.SP_MediaSeekForward)) self.prev_button = QtWidgets.QPushButton() self.prev_button.setIcon(self.style().standardIcon( QStyle.SP_MediaSeekBackward)) self.shuffle_button = QtWidgets.QPushButton("🔀") self.min_volumn = QtWidgets.QLabel("🔈") self.max_volumn = QtWidgets.QLabel("🔊") self.l_playlists = QtWidgets.QLabel("Playlists:") self.l_current_song = QtWidgets.QLabel("Current song:") self.songs = QtWidgets.QLabel("Songs:\n") self.view = QListWidget() self.viewPlaylists = QListWidget() self.all_songs = self.load_songs() self.all_playlists = self.load_playlists() self.viewPlaylists.adjustSize() self.view.adjustSize() self.view.itemClicked.connect(self.item_click) self.view.itemDoubleClicked.connect(self.item_doubleclick) self.view.installEventFilter(self) self.set_playlist() self.line_edit = QtWidgets.QLineEdit() self.line_edit.setText("") self.search_button = QtWidgets.QPushButton("search") # scroll are for list of songs self.songs_scroll_area = QtWidgets.QScrollArea() self.songs_scroll_area.setWidget(self.view) self.songs_scroll_area.setWidgetResizable(True) # scroll area for list of playlists self.playlists_scroll_area = QtWidgets.QScrollArea() self.playlists_scroll_area.setWidget(self.viewPlaylists) self.playlists_scroll_area.setWidgetResizable(True) # self.playlists_scroll_bar = QtWidgets.QScrollBar() # self.playlists_scroll_area.setVerticalScrollBar(self.playlists_scroll_bar) # self.playlists_scroll_area.setVerticalScrollBarPolicy(C.Qt.ScrollBarAlwaysOn) # set area for current song box self.current_song_area = QtWidgets.QScrollArea() self.current_song_area.setWidget(self.l_current_song) # set volumn slider self.volumn_slider = QtWidgets.QSlider(C.Qt.Horizontal) self.volumn_slider.setMaximum(100) self.volumn_slider.setMinimum(0) self.volumn_slider.setValue(50) self.volumn_slider.setTickPosition(QtWidgets.QSlider.TicksRight) self.volumn_slider.setTickInterval(10) self.seekSlider = QtWidgets.QSlider() self.seekSlider.setMinimum(0) self.seekSlider.setMaximum(100) self.seekSlider.setRange(0, self.player.duration() / 1000) self.seekSlider.setOrientation(C.Qt.Horizontal) self.seekSlider.setTracking(False) seekSliderLabel1 = QtWidgets.QLabel('0.00') seekSliderLabel2 = QtWidgets.QLabel('0.00') self.set_playlist() self.volume_change() # self.list_view = QtWidgets.QListView(self.all_songs) h_box = QtWidgets.QHBoxLayout() v_box = QtWidgets.QVBoxLayout() self.h_box4 = QtWidgets.QHBoxLayout() # h_box.addWidget(backGround) v_box.addWidget(self.all_song_button) v_box.addWidget(self.playlists_scroll_area) v_box.addWidget(self.current_song_area) self.h_box4.addWidget(seekSliderLabel1) self.h_box4.addWidget(self.seekSlider) self.h_box4.addWidget(seekSliderLabel2) h_box.addLayout(v_box) v_box1 = QtWidgets.QVBoxLayout() v_box1.addWidget(self.line_edit) v_box2 = QtWidgets.QVBoxLayout() v_box2.addWidget(self.songs_scroll_area) h_box.addLayout(v_box1) h_box1 = QtWidgets.QHBoxLayout() h_box1.addWidget(self.shuffle_button) h_box1.addWidget(self.prev_button) h_box1.addWidget(self.play_button) h_box1.addWidget(self.next_button) h_box3 = QtWidgets.QHBoxLayout() h_box3.addWidget(self.min_volumn) h_box3.addWidget(self.volumn_slider) h_box3.addWidget(self.max_volumn) h_box2 = QtWidgets.QHBoxLayout() h_box2.addWidget(self.search_button) v_box1.addLayout(h_box2) v_box1.addLayout(v_box2) v_box1.addLayout(self.h_box4) v_box1.addLayout(h_box1) v_box1.addLayout(h_box3) self.setLayout(h_box) self.setWindowTitle("Music Player") self.setGeometry(100, 100, 800, 600) self.show() self.play_button.setShortcut(' ') self.next_button.setShortcut('Alt+Right') self.prev_button.setShortcut('Alt+Left') self.search_button.setShortcut('Return') self.play_button.clicked.connect(self.play) self.next_button.clicked.connect(self.next) self.prev_button.clicked.connect(self.back) self.shuffle_button.clicked.connect(self.shuffle) self.search_button.clicked.connect(self.search) self.volumn_slider.valueChanged.connect(self.volume_change) self.all_song_button.clicked.connect(self.load_songs) self.player.currentMediaChanged.connect(self.current_song) self.player.positionChanged.connect(self.qmp_positionChanged) self.player.durationChanged.connect(self.change_duration) self.all_song_button.clicked.connect(self.show_all_songs) self.shuffled = False def add_to_playlist(self): menu = QtWidgets.QMenu() for item in self.all_playlists: menuItemTask = menu.addAction(item) menuItemTask.triggered.connect(self.picked_playlist) menu.addAction(menuItemTask) menu.exec_() def picked_playlist(self): print(self.current_item_song) # file = open(item, 'w') # file.write(self.current_item) # file.close() def create_playlist(self): root = C.QFileInfo(__file__).absolutePath() spot = (root + '/songs/') playlistName = self.getText() completeName = os.path.join(spot, f'{playlistName}.m3u') file = open(completeName, 'w') file.close() self.all_playlists.clear() self.playlists_scroll_area.update() self.all_playlists = self.load_playlists() self.playlists_scroll_area.update() def getText(self): text, okPressed = QInputDialog.getText(self, "New Playlist", "Playlist Name:", QLineEdit.Normal, "") if okPressed and text != '': return text def load_playlists(self): playlists = [] root = C.QFileInfo(__file__).absolutePath() songs = os.listdir(root + "/songs") for item in songs: if str(item[-4:]) == '.m3u': self.viewPlaylists.addItem(item[:-4]) print(root + "/songs" + item) playlists.append(root + "/songs" + item) return playlists def show_all_songs(self): self.view.clear() self.playlist.clear() root = C.QFileInfo(__file__).absolutePath() songs = os.listdir(root + "/songs") s = "Songs:\n" for item in songs: if item.endswith('.mp3'): url = C.QUrl.fromLocalFile(item) content = M.QMediaContent(url) s = (str(item[:-4])) self.view.addItem(s) self.playlist.addMedia(content) def current_song(self, media): name = media.canonicalUrl() name = name.toString() name = name.split('/') url = name[-1] url = url[:-4] self.l_current_song.setText('Current Song:\n\n' + url) self.l_current_song.adjustSize() def change_duration(self): self.seekSlider.setRange(0, self.player.duration() / 1000) def qmp_positionChanged(self, position): if self.shuffled == False: sliderLayout = self.h_box4.layout() sliderLayout.itemAt(0).widget().setText( '%d:%02d' % (int(position / 60000), int( (position / 1000) % 60))) self.seekSlider.setValue(position / 1000) sliderLayout.itemAt(2).widget().setText( '%d:%02d' % (int(self.player.duration() / 60000), int((self.player.duration() / 1000) % 60))) elif self.shuffled == True: sliderLayout = self.h_box4.layout() sliderLayout.itemAt(0).widget().setText( '%d:%02d' % (int(position / 60000), int( (position / 1000) % 60))) self.seekSlider.setValue(position / 1000) sliderLayout.itemAt(2).widget().setText( '%d:%02d' % (int(self.player.duration() / 60000), int((self.player.duration() / 1000) % 60))) def load_songs(self): songList = [] s = 'Songs:\n\n' root = C.QFileInfo(__file__).absolutePath() songs = os.listdir(root + "/songs") for item in songs: if item.endswith('.mp3'): s += (str(item[:-4]) + '\n') self.view.addItem(str(item[:-4])) song = (root + '/songs/' + item + '\n') songList.append(song) self.songs.setText(s) return songList def set_playlist(self): self.player = M.QMediaPlayer(self) self.player2 = M.QMediaPlayer(self) self.playlist = M.QMediaPlaylist(self.player) self.playlist2 = M.QMediaPlaylist(self.player2) for song in self.all_songs: url = C.QUrl.fromLocalFile(song[:-1]) content = M.QMediaContent(url) self.playlist.addMedia(content) self.playlist2.addMedia(content) self.playlist.setCurrentIndex(0) self.playlist2.shuffle() self.playlist2.setCurrentIndex(0) self.player.setPlaylist(self.playlist) self.player2.setPlaylist(self.playlist2) def play(self): if self.shuffled == False: if self.player.state() == 0 or self.player.state() == 2: self.play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) self.player.play() else: self.player.pause() self.play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) else: if self.player2.state() == 0 or self.player2.state() == 2: self.play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) self.player2.play() else: self.player2.pause() self.play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) def next(self): if self.shuffled == False: numb = self.playlist.currentIndex() self.playlist.setCurrentIndex(numb + 1) self.player.play() else: numb = self.playlist2.currentIndex() self.playlist2.setCurrentIndex(numb + 1) self.player2.play() def back(self): if self.shuffled == False: numb = self.playlist.currentIndex() self.playlist.setCurrentIndex(numb - 1) self.player.play() else: numb = self.playlist2.currentIndex() self.playlist2.setCurrentIndex(numb - 1) self.player2.play() def shuffle(self): if self.shuffled == False: self.player.stop() self.playlist2.shuffle() self.playlist2.setCurrentIndex(0) self.player2.play() self.shuffled = True elif self.shuffled == True: self.player2.stop() self.playlist.setCurrentIndex(0) self.player.play() self.shuffled = False def volume_change(self): numb = self.volumn_slider.value() self.player.setVolume(numb) self.player2.setVolume(numb) def display_song_list(self, list_of_songs): self.view.clear() if self.shuffled == False: self.playlist.clear() else: self.playlist2.clear() s = 'Songs:\n\n' for item in list_of_songs: url = C.QUrl.fromLocalFile(item) content = M.QMediaContent(url) if self.shuffled == False: self.playlist.addMedia(content) else: self.playlist2.addMedia(content) s += (str(item[:-4]) + '\n') self.view.addItem(s) def search(self): s_term = self.line_edit.text() filtered_list_of_songs = [] root = C.QFileInfo(__file__).absolutePath() songs = os.listdir(root + "/songs") # search through each song in all_songs...if it matches add to filtered_list_of_songs for song in songs: if song.lower().find(s_term.lower()) > -1: filtered_list_of_songs.append(song) self.display_song_list(filtered_list_of_songs)
class Main(QWidget): def __init__(self): super().__init__() self.setWindowTitle('akari') self.setWindowIcon(QIcon('static/icon.jpg')) # Stores the db dictionary self.db = None # Is set to 1 when metadata view of a single photo needs to be shown self.viewMetadata_flag = 0 # The full screen Image Viewer window self.imageViewer = imageViewer() # The main layout self.mainLayout = QHBoxLayout() # Add spliter to enable dyanmic resizing of taglist and imagelist self.splitter = QSplitter(Qt.Horizontal) self.leftSplitterWidget = QWidget() self.rightSplitterWidget = QWidget() self.splitter.addWidget(self.rightSplitterWidget) self.splitter.addWidget(self.leftSplitterWidget) # Initialize elements for the taglist panel self.sidePanel = QVBoxLayout() self.tagList = QListWidget() self.filerButton = QPushButton("Filter") self.clearFilerButton = QPushButton("Reset") self.sidePanel.addWidget(self.tagList) self.rightSplitterWidget.setLayout(self.sidePanel) # Initialize elements for the imagelist panel self.imagePanel = QVBoxLayout() self.imageList = QListWidget() self.imagePanelOptions = QHBoxLayout() self.fsToggle = QPushButton("View Images in FullScreen") self.quitButton = QPushButton("Quit") self.imagePanel.addWidget(self.imageList) self.leftSplitterWidget.setLayout(self.imagePanel) # Add the splitter to the main layout self.mainLayout.addWidget(self.splitter) self.setLayout(self.mainLayout) self.initUI() """ Adds button widgets to their respective panels Sets settings (like icon size) for image list """ def initUI(self): self.sidePanel.addWidget(self.filerButton, 1) self.sidePanel.addWidget(self.clearFilerButton, 1) self.tagList.setSelectionMode(QAbstractItemView.ExtendedSelection) self.imageList.setViewMode(QListView.IconMode) self.imageList.setIconSize(QSize(300, 300)) self.imageList.installEventFilter(self) self.imagePanel.addLayout(self.imagePanelOptions) self.imagePanelOptions.addWidget(self.fsToggle) self.imagePanelOptions.addWidget(self.quitButton) self.addEventHandlers() self.show() def updateDB(self, db): self.db = db """ Adds event handlers for various buttons """ def addEventHandlers(self): self.filerButton.clicked.connect(self.filterButton_pressed) self.clearFilerButton.clicked.connect(self.clearFilterButton_pressed) self.fsToggle.clicked.connect(self.fsToggle_pressed) self.quitButton.clicked.connect(self.close) self.imageList.itemDoubleClicked.connect(self.viewMetadata) """ Adds the buttons necessary to allow users to add new tags and remove tags These are only shown if `viewMetadata_flag` is set to 1 """ def addTagEditingButtons(self): self.addTagsButton = QPushButton("Add New Tag") self.removeTagsButton = QPushButton("Remove Selected Tags") self.sidePanel.addWidget(self.addTagsButton) self.sidePanel.addWidget(self.removeTagsButton) self.addTagsButton.clicked.connect(self.addTagsButton_pressed) self.removeTagsButton.clicked.connect(self.removeTagsButton_pressed) """ Removes the buttons necessary to allow users to add new tags and remove tags """ def removeTagEditingButtons(self): try: self.viewMetadata_flag = 0 self.sidePanel.removeWidget(self.addTagsButton) self.addTagsButton.deleteLater() self.sidePanel.removeWidget(self.removeTagsButton) self.removeTagsButton.deleteLater() except: pass """ Refreshes the taglist for a given image Called when user adds or removes some tags from an image """ def refreshTagListForImage(self, image): self.tagList.clear() for tag in self.db[image]: self.tagList.addItem(tag) """ This function is called when user presses the "View Imagesin FullScreen" button """ def fsToggle_pressed(self): self.imageViewer.set_imageNumber(0) imageViewer_list = [] for x in range(self.imageList.count()): imageViewer_list.append(self.imageList.item(x).data(0)) if len(imageViewer_list) > 0: self.imageViewer.set_imageViewerList(imageViewer_list) self.imageViewer.addImage(imageViewer_list[0]) self.imageViewer.showFullScreen() """ This function is called when user presses the "Add Tags" button in the metadata view """ def addTagsButton_pressed(self): image = self.imageList.item(0).data(0) tag, okPressed = QInputDialog.getText(self, "Enter Tag", "Enter Tag", QLineEdit.Normal, "") if okPressed and tag != '': if tag not in self.db[image]: self.db[image].append(tag) if tag in self.db['akari-tags']: self.db['akari-tags'][tag] = self.db['akari-tags'][tag] + 1 else: self.db['akari-tags'][tag] = 1 commit_changes(self.db) self.db = loadDB() self.refreshTagListForImage(image) """ This function is called when user presses the "Remove Selected Tags" button in the metadata view """ def removeTagsButton_pressed(self): tags_to_remove = self.tagList.selectedItems() image = self.imageList.item(0).data(0) for tag in tags_to_remove: tag = tag.text() self.db[image].remove(tag) self.db['akari-tags'][tag] = self.db['akari-tags'][tag] - 1 if self.db['akari-tags'][tag] == 0: self.db['akari-tags'].pop(tag, None) commit_changes(self.db) self.db = loadDB() self.refreshTagListForImage(image) """ This function is called when user presses the "Filter" button """ def filterButton_pressed(self): selected_items = self.tagList.selectedItems() selected_tags = [] filtered_images = [] for i in range(len(selected_items)): selected_tags.append( str(self.tagList.selectedItems()[i].text()).split(" ")[-1]) for imgPath in self.db: if imgPath == 'akari-tags': continue if all(elem in self.db[imgPath] for elem in selected_tags): if imgPath not in filtered_images: filtered_images.append(imgPath) self.addImages(filtered_images) self.removeTagEditingButtons() """ This function is called when user presses the "Reset" button """ def clearFilterButton_pressed(self): imgList = self.getImgList() self.addImages(imgList) self.addTags() self.removeTagEditingButtons() """ This function is called when user selects the "View Metadata" Option in the context menu """ def viewMetadata(self): if self.viewMetadata_flag == 0: self.viewMetadata_flag = 1 selected_image = self.imageList.selectedItems()[0].data(0) tags = self.db[selected_image] self.tagList.clear() self.tagList.addItems(tags) self.addImages([selected_image]) self.addTagEditingButtons() """ Override funtion to add "View Metadata" option in the context menu """ def contextMenuEvent(self, event): menu = QMenu(self) if self.imageList.selectedItems() != []: viewMetadata = menu.addAction("View Metadata") action = menu.exec_(self.mapToGlobal(event.pos())) if action == viewMetadata: self.viewMetadata() """ Returns a list of images from the db dictionary """ def getImgList(self): imgList = [] for imgPath in self.db: if imgPath == 'akari-tags': continue imgList.append(imgPath) return imgList """ Clears all the images in the image panel and adds images from `imgList` """ def addImages(self, imgList): self.imageList.clear() for imgPath in imgList: item = QListWidgetItem() icon = QIcon() pixmap = QPixmap(imgPath) icon.addPixmap(pixmap) item.setFont(QFont("Times", 1)) item.setForeground(QColor("white")) item.setText(imgPath) item.setIcon(icon) self.imageList.addItem(item) """ Clears all the tags in the tag panel and adds all the tags along with their count from the db dictionary """ def addTags(self): self.tagList.clear() tagList = [] for tag in sorted(self.db['akari-tags'], key=self.db['akari-tags'].get, reverse=True): tagList.append(str(self.db['akari-tags'][tag]) + ' ' + tag) self.tagList.addItems(tagList)
class AudioPlayerPage(QWidget): about_play_audio = pyqtSignal(str) def __init__(self): super().__init__() self.audio_list_widget = QListWidget() self.audio_list_widget.installEventFilter(self) self.audio_list_widget.itemDoubleClicked.connect(self.play) # TODO: playlist объединить с audio_list_widget (см примеры работы с QMediaPlayer) self.playlist = QMediaPlaylist() self.playlist.currentIndexChanged.connect( lambda row: self.audio_list_widget.setCurrentRow(row)) # TODO: обрабатывать сигналы плеера: http://doc.qt.io/qt-5/qmediaplayer.html#signals self.player = QMediaPlayer() self.player.setPlaylist(self.playlist) self.player.currentMediaChanged.connect( lambda media: self.about_play_audio.emit(self.audio_list_widget. currentItem().text())) if not self.player.isAvailable(): # TODO: перевод text = "The QMediaPlayer object does not have a valid service.\n" \ "Please check the media service plugins are installed." log.warning(text) QMessageBox.warning(self, "Service not available", text) quit() self.controls = PlayerControls(self.player) self.controls.set_state(self.player.state()) self.controls.set_volume(self.player.volume()) self.controls.set_muted(self.controls.is_muted()) self.controls.play_signal.connect(self.play) self.controls.pause_signal.connect(self.player.pause) self.controls.stop_signal.connect(self.player.stop) self.controls.next_signal.connect(self.playlist.next) self.controls.previous_signal.connect(self.playlist.previous) self.controls.change_volume_signal.connect(self.player.setVolume) self.controls.change_muting_signal.connect(self.player.setMuted) self.progress = QProgressBar() self.progress.hide() layout = QVBoxLayout() layout.addWidget(self.controls) layout.addWidget(self.audio_list_widget) layout.addWidget(self.progress) self.setLayout(layout) self.thread = LoadAudioListThread() self.thread.about_add_audio.connect(self._add_audio) self.thread.about_progress.connect(self.progress.setValue) self.thread.about_range_progress.connect(self.progress.setRange) self.thread.started.connect(self._start) self.thread.finished.connect(self._finished) def _add_audio(self, title, url): item = QListWidgetItem(title) item.setData(Qt.UserRole, url) self.audio_list_widget.addItem(item) self.playlist.addMedia(QMediaContent(QUrl(url))) # При добавлении первой аудизаписи, вызываем воспроизведение if self.audio_list_widget.count() == 1: self.audio_list_widget.setCurrentRow(0) self.playlist.setCurrentIndex(0) self.play() def _start(self): self.audio_list_widget.clear() self.playlist.clear() self.progress.show() def _finished(self): self.progress.hide() def fill(self, vk): self.thread.vk = vk # Если поток запущен, останавливаем его, иначе -- запускаем if self.thread.isRunning(): self.thread.exit() else: self.thread.start() def play(self): if self.playlist.currentIndex() != self.audio_list_widget.currentRow(): self.playlist.setCurrentIndex(self.audio_list_widget.currentRow()) self.player.play() def eventFilter(self, obj, event): # Воспроизведение видео при клике на кнопки Enter/Return в плейлисте if obj == self.audio_list_widget and event.type( ) == QKeyEvent.KeyPress: if self.audio_list_widget.hasFocus() and event.key( ) == Qt.Key_Return or event.key() == Qt.Key_Enter: item = self.audio_list_widget.currentItem() if item is not None: self.play() return super().eventFilter(obj, event)
class MainWindow(CenterWindow): """ Displays list with tasks assigned to current user in JIRA """ def __init__(self, controller): super().__init__() self.setStyleSheet(QSS) self.controller = controller self.resize(1000, 600) self.setWindowTitle('JIRA Quick Reporter') self.setWindowIcon(QIcon(LOGO_PATH)) self.center() self.current_item = None self.vbox = QVBoxLayout() self.save_btn_box = QHBoxLayout() self.filter_name_label = QLabel() self.filter_name_label.setObjectName('filter_name_label') self.filter_name_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.filter_name_label.setAlignment(Qt.AlignLeft) self.filter_edited_label = QLabel('-> edited') self.filter_edited_label.setObjectName('filter_edited_label') self.filter_edited_label.hide() self.filter_edited_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.save_filter_btn = QPushButton('Save as') self.save_filter_btn.setObjectName('save_filter_btn') self.save_filter_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.save_filter_btn.clicked.connect(self.controller.save_filter) self.overwrite_filter_button = QPushButton('Save') self.overwrite_filter_button.setToolTip('You need to edit filter query first') self.overwrite_filter_button.setObjectName('save_filter_btn') self.overwrite_filter_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.overwrite_filter_button.clicked.connect(lambda: self.controller.save_filter(True)) self.delete_filter_btn = QPushButton() self.delete_filter_btn.setObjectName('delete_filter_btn') self.delete_filter_btn.clicked.connect(self.delete_filter) self.delete_filter_btn.setIcon(QIcon(DELETE_FILTER_ICON)) self.delete_filter_btn.setIconSize( QSize( self.delete_filter_btn.sizeHint().height(), self.delete_filter_btn.sizeHint().height() ) ) self.delete_filter_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.delete_filter_btn.setToolTip('Delete filter') self.save_btn_box.addWidget(self.filter_name_label, Qt.AlignLeft) self.save_btn_box.addWidget(self.filter_edited_label, Qt.AlignLeft) self.save_btn_box.addWidget(self.save_filter_btn, Qt.AlignLeft) self.save_btn_box.addWidget(self.overwrite_filter_button, Qt.AlignLeft) self.save_btn_box.addStretch() self.save_btn_box.addWidget(self.delete_filter_btn, Qt.AlignRight) self.create_filter_box = QHBoxLayout() self.query_field = QLineEdit() self.query_field.setObjectName('query_field') self.query_field.setPlaceholderText('You need to write a query here') self.action_help = QAction() self.action_help.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxQuestion)) self.help_filter_url = QUrl(FILTER_FIELD_HELP_URL) self.action_help.triggered.connect(self.filter_field_help) self.query_field.addAction(self.action_help, QLineEdit.TrailingPosition) self.query_field.installEventFilter(self) self.search_issues_button = QPushButton('Search') self.search_issues_button.setObjectName('search_issues_button') self.search_issues_button.clicked.connect(self.controller.search_issues_by_query) self.create_filter_box.addWidget(self.query_field) self.create_filter_box.addWidget(self.search_issues_button) self.list_box = QVBoxLayout() self.issue_list_widget = QListWidget() self.issue_list_widget.setObjectName('issue_list') self.label_info = QLabel('You have no issues.') self.label_info.setAlignment(Qt.AlignCenter) self.list_box.addWidget(self.issue_list_widget) self.list_box.addWidget(self.label_info) self.label_info.hide() self.vbox.addLayout(self.save_btn_box) self.vbox.addLayout(self.create_filter_box) self.vbox.addLayout(self.list_box) self.filters_frame = QFrame() self.filters_frame.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) self.filters_frame.setFrameShape(QFrame.StyledPanel) self.filters_frame.setObjectName('filters_frame') self.filters_box = QVBoxLayout(self.filters_frame) self.filters_box_label = QLabel('Issues and filters') self.filters_box_label.setObjectName('filters_box_label') self.filters_box.addWidget(self.filters_box_label) self.filters_list = QListWidget() self.filters_list.installEventFilter(self) self.filters_list.itemClicked.connect(self.on_filter_selected) self.filters_list.setObjectName('filters_list') self.filters_list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.filters_box.addWidget(self.filters_list) self.add_filter_button = QPushButton('+') self.add_filter_button.clicked.connect(self.add_filter_btn_click) self.add_filter_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.filters_box.addWidget(self.add_filter_button, alignment=Qt.AlignRight) self.btn_box = QHBoxLayout() self.refresh_btn = QPushButton('Refresh') self.refresh_btn.clicked.connect(self.controller.refresh_issue_list) self.btn_box.addWidget(self.refresh_btn, alignment=Qt.AlignRight) self.vbox.addLayout(self.btn_box) self.toggle_frame_filters_btn = QPushButton('<') self.toggle_frame_filters_btn.clicked.connect(self.toggle_frame_filters) self.toggle_frame_filters_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.toggle_frame_filters_btn.setObjectName('toggle_filters_btn') self.main_box = QHBoxLayout() self.main_box.addWidget(self.filters_frame) self.main_box.addWidget(self.toggle_frame_filters_btn, alignment=Qt.AlignTop) self.main_box.addLayout(self.vbox) self.setLayout(self.main_box) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon(LOGO_PATH)) self.tray_menu = QMenu() self.action_open = QAction('Open JQR', self) self.action_quit = QAction('Quit JQR', self) self.tray_menu.addAction(self.action_open) self.action_open.triggered.connect(self.show_jqr_from_tray) self.tray_menu.addAction(self.action_quit) self.action_quit.triggered.connect(self.controller.quit_app) self.tray_icon.setContextMenu(self.tray_menu) self.tray_icon.show() self.timer_log_work = QTimer() self.timer_log_work.timeout.connect(self.notification_to_log_work) self.timer_log_work.start(LOG_TIME) self.timer_refresh = QTimer() self.timer_refresh.timeout.connect(self.controller.auto_refresh_issue_list) def show_jqr_from_tray(self): self.hide() self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint) self.activateWindow() self.show() def keyPressEvent(self, event): if event.key() == Qt.Key_Return: self.controller.search_issues_by_query() def notification_to_log_work(self): QSound.play(RING_SOUND_PATH) self.tray_icon.showMessage( '1 hour had passed', 'Don\'t forget to log your work!', msecs=2000 ) self.timer_log_work.start(LOG_TIME) def update_issues(self, update_list): for issue in update_list: item = self.issue_list_widget.findItems( issue['key'], Qt.MatchExactly )[0] item.setText(issue['key']) issue_widget = self.issue_list_widget.itemWidget(item) issue_widget.set_issue_key(issue['key'], issue['link']) issue_widget.set_issue_title(issue['title']) issue_widget.set_time( issue['estimated'], issue['logged'], issue['remaining'] ) issue_widget.set_workflow.clear() issue_widget.set_workflow.addItems(self.controller.get_possible_workflows(issue)) issue_widget.set_workflow.setCurrentIndex(0) issue_widget.set_workflow.activated[str].disconnect() issue_widget.set_workflow.activated[str].connect( partial( self.controller.change_workflow, issue['workflow'], issue['issue_obj'], ) ) def delete_issues(self, delete_list): for issue in delete_list: item = self.issue_list_widget.findItems( issue['key'], Qt.MatchExactly )[0] self.issue_list_widget.takeItem( self.issue_list_widget.row(item) ) def insert_issues(self, new_issues_list): for issue in new_issues_list: issue_widget = QCustomWidget() issue_widget.set_issue_key(issue['key'], issue['link']) issue_widget.set_issue_title(issue['title']) issue_widget.set_time( issue['estimated'], issue['logged'], issue['remaining'] ) issue_widget.quick_log_btn.clicked.connect( partial( self.controller.log_work_from_list, issue['key'] ) ) issue_widget.log_work_btn.clicked.connect( partial( self.controller.open_time_log, issue['key'] ) ) issue_widget.open_pomodoro_btn.clicked.connect( partial( self.controller.open_pomodoro_window, issue['key'], issue['title'] ) ) # add workflow statuses to dropdown possible_workflows = self.controller.get_possible_workflows(issue) issue_widget.set_workflow.addItems(possible_workflows) issue_widget.set_workflow.setCurrentIndex(0) issue_widget.set_workflow.activated[str].connect( partial( self.controller.change_workflow, issue['workflow'], issue['issue_obj'], ) ) # add issue item to list issue_list_widget_item = QListWidgetItem() issue_list_widget_item.setText(issue['key']) issue_list_widget_item.setSizeHint(issue_widget.sizeHint()) self.issue_list_widget.insertItem(issue['index'], issue_list_widget_item) self.issue_list_widget.setItemWidget( issue_list_widget_item, issue_widget ) self.set_size_hint() def set_size_hint(self): self.issue_list_widget.setMinimumWidth( self.issue_list_widget.sizeHintForColumn(0) + 50 ) self.issue_list_widget.setMinimumHeight( self.issue_list_widget.sizeHintForRow(0) * 2 ) def show_filters(self, filters_dict): for index, key in enumerate(filters_dict): if key == SEARCH_ITEM_NAME: self.filters_list.insertItem(0, key) else: self.filters_list.addItem(key) self.filters_list.item(index).setToolTip(key) self.filters_list.item(0).setText(self.filters_list.item(0).text().capitalize()) # add separator after first item separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setObjectName('separator') item_separator = QListWidgetItem() item_separator.setFlags(Qt.NoItemFlags) self.filters_list.insertItem(1, item_separator) self.filters_list.setItemWidget(item_separator, separator) self.filters_list.setCurrentItem( self.filters_list.findItems( MY_ISSUES_ITEM_NAME, Qt.MatchExactly )[0]) self.filters_list.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) self.on_filter_selected(self.filters_list.currentItem()) def filter_field_help(self): QDesktopServices.openUrl(self.help_filter_url) def on_filter_selected(self, item): if not item.text(): return self.current_item = item if len(self.current_item.text()) > 50: set_text = '{}...'.format(self.current_item.text()[:50]) else: set_text = self.current_item.text() self.issue_list_widget.scrollToTop() self.controller.search_issues_by_filter_name(item.text()) self.filter_name_label.setText(set_text) self.filter_edited_label.hide() if self.filters_list.currentItem().text() == MY_ISSUES_ITEM_NAME: self.save_filter_btn.hide() self.overwrite_filter_button.hide() self.filter_edited_label.hide() self.delete_filter_btn.hide() elif self.filters_list.currentItem().text() == SEARCH_ITEM_NAME.capitalize(): # activate save button self.overwrite_filter_button.hide() self.save_filter_btn.show() self.delete_filter_btn.hide() else: # activate overwrite button self.overwrite_filter_button.show() self.overwrite_filter_button.setEnabled(False) self.save_filter_btn.hide() self.delete_filter_btn.show() def toggle_frame_filters(self): if self.toggle_frame_filters_btn.text() == '<': self.toggle_frame_filters_btn.setText('>') self.filters_frame.hide() else: self.toggle_frame_filters_btn.setText('<') self.filters_frame.show() def add_filter_btn_click(self): self.overwrite_filter_button.hide() self.save_filter_btn.show() self.delete_filter_btn.hide() self.filter_edited_label.hide() self.filters_list.setCurrentItem(None) self.query_field.setText('') self.filter_name_label.setText('Add new filter') self.controller.current_issues.clear() self.show_no_issues() def eventFilter(self, obj, event): # if user started typing in filter field if obj is self.query_field and event.type() == QEvent.KeyRelease: if not self.filters_list.currentItem(): return super().eventFilter(obj, event) current_filter_name = self.filters_list.currentItem().text().lower() # if current filter is not 'Search issues' or 'my open issues' if current_filter_name not in (SEARCH_ITEM_NAME, MY_ISSUES_ITEM_NAME): # if query of current filter has not changed if self.controller.filters_handler.get_filter_by_name( current_filter_name ) != self.query_field.text(): # show that filter has been edited self.filter_edited_label.show() self.overwrite_filter_button.setEnabled(True) else: self.filter_edited_label.hide() self.overwrite_filter_button.setEnabled(False) return super().eventFilter(obj, event) def set_current_filter(self, filter_name): items = self.filters_list.findItems( filter_name, Qt.MatchExactly ) self.filters_list.setCurrentItem(items[0]) self.on_filter_selected(items[0]) def add_filter(self, filter_name): self.filters_list.addItem(filter_name) self.set_current_filter(filter_name) def delete_filter(self): filter_name = self.filters_list.currentItem().text() reply = QMessageBox.question( self, 'Delete filter', "Are you sure you want to delete " "'{}' filter?".format(filter_name), QMessageBox.Yes | QMessageBox.Cancel ) if reply == QMessageBox.Yes: self.controller.filters_handler.delete_filter(filter_name) self.filters_list.takeItem( self.filters_list.currentRow() ) self.filters_list.setCurrentItem( self.filters_list.findItems( MY_ISSUES_ITEM_NAME, Qt.MatchExactly )[0]) self.on_filter_selected(self.filters_list.currentItem()) def show_no_issues(self, error_text=None): self.issue_list_widget.clear() self.issue_list_widget.hide() if error_text: self.label_info.setText(error_text) self.label_info.show() def set_workflow_current_state(self, issue_key): item = self.issue_list_widget.findItems( issue_key, Qt.MatchExactly )[0] custom_item = self.issue_list_widget.itemWidget(item) custom_item.set_workflow.setCurrentIndex(0) def wheelEvent(self, event): # top left corner coordinates of the issue list list_pos = self.issue_list_widget.pos() # check if cursor position is on the issue list if event.pos().x() >= list_pos.x() and event.pos().y() >= list_pos.y(): if event.angleDelta().y() < 0: self.controller.refresh_issue_list(True) event.accept() def closeEvent(self, event): event.ignore() self.hide()
class HeaderEditDialog(QDialog): # name of the current preset, whether to set this preset as default, list of Columns header_changed = pyqtSignal(str, bool, list) def __init__(self, parent, table_header): super().__init__(parent) self.table_header = table_header self.default_preset_name = None self.preset_name = table_header.preset_name self.columns = copy.deepcopy(table_header.columns) self.setupUi() def setupUi(self): self.resize(200, 400) self.vbox = QVBoxLayout(self) self.presetLabel = QLabel("Preset: {}".format(self.preset_name), self) self.columnList = QListWidget(self) self.setAsDefaultCheckbox = QCheckBox("Set as default preset", self) self.vbox.addWidget(self.presetLabel) self.vbox.addWidget(self.columnList) self.vbox.addWidget(self.setAsDefaultCheckbox) self.columnList.setDragDropMode(QListWidget.InternalMove) self.columnList.setDefaultDropAction(Qt.MoveAction) self.columnList.setSelectionMode(QListWidget.ExtendedSelection) self.columnList.setAlternatingRowColors(True) self.columnList.installEventFilter(self) self.columnList.setContextMenuPolicy(Qt.CustomContextMenu) self.columnList.customContextMenuRequested.connect(self.open_menu) self.columnList.model().rowsMoved.connect(self.read_columns_from_list) # for a dumb qss hack to make selected checkboxes not white on a light theme self.columnList.setObjectName("ColumnList") self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel, self) self.vbox.addWidget(self.buttonBox) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.fill_column_list() self.set_default_checkbox() def eventFilter(self, object, event): if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return: self.toggle_selected_columns() return True return False def fill_column_list(self): self.columnList.clear() for column in self.columns: ColumnListItem(self.columnList, column) def accept(self): self.read_columns_from_list() self.header_changed.emit(self.preset_name, self.setAsDefaultCheckbox.isChecked(), self.columns) self.done(0) def reject(self): self.done(0) def read_columns_from_list(self): new_columns = [] for i in range(self.columnList.count()): item = self.columnList.item(i) new_columns.append(item.column) self.columns = new_columns def toggle_selected_columns(self): selected = self.columnList.selectedItems() for item in selected: value_now = item.data(Qt.CheckStateRole) item.setData(Qt.CheckStateRole, not value_now) self.columnList.reset() # @Improvement: is there a better way to update QListWidget? def open_menu(self, position): menu = QMenu(self) preset_menu = menu.addMenu('Presets') preset_menu.addAction('New preset', self.new_preset_dialog) preset_menu.addSeparator() preset_names = CONFIG.get_header_presets() if len(preset_names) == 0: action = preset_menu.addAction('No presets') action.setEnabled(False) else: delete_menu = menu.addMenu('Delete preset') for name in preset_names: preset_menu.addAction(name, partial(self.load_preset, name)) delete_menu.addAction(name, partial(self.delete_preset, name)) menu.addSeparator() menu.addAction('New column...', self.create_new_column_dialog) if len(self.columnList.selectedIndexes()) > 0: menu.addAction('Delete selected', self.delete_selected) menu.popup(self.columnList.viewport().mapToGlobal(position)) def load_preset(self, name): new_columns = CONFIG.load_header_preset(name) if not new_columns: return self.columns = new_columns self.preset_name = name self.fill_column_list() self.presetLabel.setText("Preset: {}".format(name)) self.set_default_checkbox() def new_preset_dialog(self): d = QInputDialog(self) d.setLabelText('Enter the new name for the new preset:') d.setWindowTitle('Create new preset') d.textValueSelected.connect(self.create_new_preset) d.open() def create_new_preset(self, name): if name in CONFIG.get_header_presets(): show_warning_dialog(self, "Preset creation error", 'Preset named "{}" already exists.'.format(name)) return if len(name.strip()) == 0: show_warning_dialog(self, "Preset creation error", 'This preset name is not allowed.'.format(name)) return self.preset_name = name self.presetLabel.setText("Preset: {}".format(name)) CONFIG.save_header_preset(name, self.columns) self.setAsDefaultCheckbox.setChecked(False) def delete_preset(self, name): CONFIG.delete_header_preset(name) if name == self.preset_name: self.columns = copy.deepcopy(DEFAULT_COLUMNS) self.fill_column_list() def create_new_column_dialog(self): d = CreateNewColumnDialog(self) d.add_new_column.connect(self.add_new_column) d.setWindowTitle('Create new column') d.open() def add_new_column(self, name, title): new_column = Column(name, title) # if the last column is message, insert this column before it (i think it makes sense?) if self.columns[-1].name == 'message': self.columns.insert(-1, new_column) else: self.columns.append(new_column) self.fill_column_list() def set_default_checkbox(self): self.setAsDefaultCheckbox.setChecked(CONFIG['default_header_preset'] == self.preset_name) def delete_selected(self): selected = self.columnList.selectedItems() for item in selected: self.columnList.takeItem(self.columnList.row(item)) self.read_columns_from_list() self.fill_column_list()
class Example(QWidget): def __init__(self): super().__init__() self.filenames = json_files() if type(self.filenames) is list: self.curr_file = self.filenames[0] else: self.curr_file = self.filenames self.initUI() def initUI(self): self.num = -1 # index for search bar query self.show_save = False # bool for showing unsaved changes dialog self.temp_file = ".temp.csv" self.refresh_file = False # failsafe 1 for itemChanged trigger self.list_1 = QListWidget() lister(file=self.curr_file, target=self.list_1, index=0, mode=0) self.list_1.clicked.connect(self.clear_selection) self.list_1.installEventFilter(self) self.list_items = self.list_1.count( ) # failsafe 2 for itemChanged trigger self.list_1.itemChanged.connect(self.edit_next_item) self.list_1.verticalScrollBar().valueChanged.connect(self.sync_scroll) self.list_2 = QListWidget() lister(file=self.curr_file, target=self.list_2, index=1, mode=0) self.list_2.clicked.connect(self.clear_selection) self.list_3 = QListWidget() lister(file=self.curr_file, target=self.list_3, index=2, mode=0) self.list_3.clicked.connect(self.clear_selection) self.all_lists = [self.list_1, self.list_2, self.list_3] self.menubar = QMenuBar() self.menubar.setNativeMenuBar(False) exit_event = QAction('Exit', self) exit_event.setShortcut('Ctrl+W') exit_event.triggered.connect(app.quit) showAct = QAction('Show extras', self, checkable=True) showAct.setChecked(False) showAct.setShortcut('Ctrl+E') showAct.triggered.connect(self.hide_notes) addAct = QAction('Fields', self) addAct.setShortcut('Ctrl+N') addAct.triggered.connect(self.add_item) fileOpen = QAction('Open file', self) fileOpen.triggered.connect(self.fileDialog) fileOpen.setShortcut('Ctrl+O') fileSave = QAction('Save file', self) fileSave.triggered.connect(self.save) fileSave.triggered.connect(self.refresh_recents) fileSave.setShortcut('Ctrl+S') self.fileRecents = QMenu('Recent file', self) self.refresh_recents() self.toggle_theme = QAction('Toggle theme', self, checkable=True) self.toggle_theme.setChecked(json_theme()) self.toggle_theme.triggered.connect(self.theme) self.toggle_theme.setShortcut('Ctrl+T') self.col_sort_index = QMenu('Sorting column index', self) self.col_sort_index.addAction(QAction(str(0), self)) self.col_sort_index.addAction(QAction(str(1), self)) self.col_sort_index.addAction(QAction(str(2), self)) self.col_sort_index.triggered.connect(self.sort_col_choice) self.col_search_index = QMenu('Searching column index', self) self.col_search_index.addAction(QAction(str(0), self)) self.col_search_index.addAction(QAction(str(1), self)) self.col_search_index.addAction(QAction(str(2), self)) self.col_search_index.triggered.connect(self.search_col_choice) self.sort = QAction('Sort entries', self, checkable=True) self.curr_col = 0 self.search_col = 0 self.sort.triggered.connect(self.refresh_list) self.sort.setShortcut('Ctrl+R') self.addFields = self.menubar.addMenu('Add') self.addFields.addAction(addAct) self.optionMenu = self.menubar.addMenu('Options') self.optionMenu.addAction(exit_event) self.optionMenu.addAction(showAct) self.optionMenu.addAction(self.toggle_theme) self.optionMenu.addMenu(self.col_sort_index) self.optionMenu.addMenu(self.col_search_index) self.optionMenu.addAction(self.sort) self.fileMenu = self.menubar.addMenu('File') self.fileMenu.addAction(fileOpen) self.fileMenu.addAction(fileSave) self.fileMenu.addMenu(self.fileRecents) self.search_bar = QLineEdit() self.search_bar.setPlaceholderText('Search vocab') self.search_bar.setClearButtonEnabled(True) self.search_bar.setMaxLength(10) self.search_bar.returnPressed.connect(self.search_item) self.status_bar = QStatusBar() status(self.status_bar, self.list_1) grid = QGridLayout() grid.setSpacing(10) grid.addWidget(self.menubar, 0, 0) grid.addWidget(self.list_1, 1, 0) grid.addWidget(self.list_2, 1, 1) grid.addWidget(self.list_3, 1, 2) grid.addWidget(self.search_bar, 0, 1) grid.addWidget(self.status_bar) self.theme() self.setLayout(grid) self.setGeometry(*json_window_size()) self.setWindowTitle(f'{split_name(self.curr_file)}') self.show() self.list_1.scrollToBottom() self.list_2.verticalScrollBar().setHidden(True) self.list_3.verticalScrollBar().setHidden(True) self.list_3.setHidden(True) def sync_scroll(self): scroll_location = self.list_1.verticalScrollBar().value() self.list_2.verticalScrollBar().setValue(scroll_location) self.list_3.verticalScrollBar().setValue(scroll_location) def edit_next_item(self, event): """When an item is added and edited on the first col, starts editing its counterpart on the next col""" if self.list_items == self.list_1.count( ) - 2 or self.list_items != self.list_1.count( ) and self.refresh_file == False: item = self.list_2.item(self.list_2.count() - 1) self.list_2.editItem(item) self.list_items = self.list_1.count() def closeEvent(self, event): """Triggered upon program exit, shows a dialog for unsaved changes using a bool""" if self.show_save == True: reply = QMessageBox.question( self, 'Message', "You may have unsaved changes, are you sure you want to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: try: remove(self.temp_file) except: pass event.accept() else: event.ignore() else: pass def sort_col_choice(self, action): self.curr_col = int(action.text()) def search_col_choice(self, action): self.search_col = int(action.text()) def refresh_list(self): """Refreshes the contents of the lists, when sorting is used""" self.save( mode=1 ) # saves a temp copy, with changes, but irreversable sorting introduced clear_lists(self.all_lists) if self.sort.isChecked() == True: mode = 2 else: mode = 0 try: lister(file=self.temp_file, target=self.list_1, index=0, mode=mode, column=self.curr_col) lister(file=self.temp_file, target=self.list_2, index=1, mode=mode, column=self.curr_col) lister(file=self.temp_file, target=self.list_3, index=2, mode=mode, column=self.curr_col) except: lister(file=self.curr_file, target=self.list_1, index=0, mode=mode, column=self.curr_col) lister(file=self.curr_file, target=self.list_2, index=1, mode=mode, column=self.curr_col) lister(file=self.curr_file, target=self.list_3, index=2, mode=mode, column=self.curr_col) def refresh_recents(self): try: file_1 = QAction(self.curr_file, self) self.fileRecents.addAction(file_1) file_1.triggered.connect(self.clickedFileAct) if type(self.filenames) is list: if self.filenames[1] != None: file_2 = QAction(self.filenames[1], self) self.fileRecents.addAction(file_2) file_2.triggered.connect(self.clickedFileAct) if self.filenames[2] != None: file_3 = QAction(self.filenames[2], self) self.fileRecents.addAction(file_3) file_3.triggered.connect(self.clickedFileAct) except: pass def clickedFileAct(self): self.refresh_file = True file = self.sender().text() self.curr_file = file self.setWindowTitle(f'{split_name(self.curr_file)}') clear_lists(self.all_lists) lister(file=self.curr_file, target=self.list_1, index=0) lister(file=self.curr_file, target=self.list_2, index=1) lister(file=self.curr_file, target=self.list_3, index=2) status(self.status_bar, self.list_1) self.theme() self.list_1.scrollToBottom() self.list_3.setHidden(True) self.refresh_file = False def eventFilter(self, source, event): """Item (row) deletion""" if (event.type() == QEvent.ContextMenu and source is self.list_1): menu = QMenu() menu.addAction("Delete row") if menu.exec_(event.globalPos()): item = source.itemAt(event.pos()) try: model = self.list_1.indexFromItem(item) row = model.row() self.show_save = True self.list_1.takeItem(row) self.list_2.takeItem(row) self.list_3.takeItem(row) status(self.status_bar, self.list_1, f'Deleted row number: {row+1}.') self.clearSelection() except: pass return True return super(Example, self).eventFilter(source, event) def hide_notes(self): """Toggles showing the note column and stretches the window for clearer reading of it""" self.list_3.setHidden(not self.list_3.isHidden()) def theme(self): """Sets the theme for the window and its widgets""" palette = QPalette() # dark theme if self.toggle_theme.isChecked() == True: palette.setColor(QPalette.Window, QColor(0, 0, 0)) dark = "background-color: rgb(0, 0, 0); color: rgb(255, 255, 255);" self.menubar.setStyleSheet(dark) self.addFields.setStyleSheet(dark) self.optionMenu.setStyleSheet(dark) self.fileMenu.setStyleSheet(dark) self.search_bar.setStyleSheet( "background-color: rgb(0, 0, 0); color: rgb(255, 255, 255)" ) # border: 0px; for transparency self.status_bar.setStyleSheet(dark) style_items(self.all_lists, dark_theme=True) # light theme elif self.toggle_theme.isChecked() == False: palette.setColor(QPalette.Window, QColor(255, 255, 255)) light = "background-color: rgb(255, 255, 255); color: rgb(0, 0, 0)" self.menubar.setStyleSheet(light) self.addFields.setStyleSheet(light) self.optionMenu.setStyleSheet(light) self.fileMenu.setStyleSheet(light) self.search_bar.setStyleSheet(light) self.status_bar.setStyleSheet(light) style_items(self.all_lists, dark_theme=False) self.setPalette(palette) self.theme_bool = self.toggle_theme.isChecked( ) # used in the save func def search_item(self): """Takes input from the search bar and matches with an item, gets index and scrolls to it, more reusults being qued with the num class var """ query = self.search_bar.text() search = self.all_lists[self.search_col].findItems( query, Qt.MatchContains) status(self.status_bar, self.list_1, f'Found {len(search)} results.') self.clear_selection() # testing search in all column # search_list =[] # for x in range(3): # search_list.append(self.all_lists[x].findItems(query, Qt.MatchContains)) # parent_list = [] # for x in range(3): # for y in range(len(search_list[x])): # parent_list.append(self.all_lists[x]) # replace with x # import itertools # merged = list(itertools.chain.from_iterable(search_list)) # search_dict = dict(zip(parent_list, merged)) # print(search_dict) # print() # print(len(merged)) # print(len(parent_list)) self.num += 1 for i in search: try: model_index = self.all_lists[self.search_col].indexFromItem( search[self.num]) except: self.num = 0 model_index = self.all_lists[self.search_col].indexFromItem( search[self.num]) item_index = model_index.row() self.all_lists[self.search_col].item(item_index).setSelected(True) self.list_1.scrollToItem(self.list_1.item(item_index), QAbstractItemView.PositionAtCenter) def add_item(self): self.show_save = True for x in range(3): if x == 0: lister(file=self.curr_file, target=self.list_1, index=x, mode=1) elif x == 1: lister(file=self.curr_file, target=self.list_2, index=x, mode=1) elif x == 2: lister(file=self.curr_file, target=self.list_3, index=x, mode=1) item = self.list_1.item(self.list_1.count() - 1) self.list_1.editItem(item) status(self.status_bar, self.list_1) self.list_1.scrollToBottom() self.list_2.scrollToBottom() self.list_3.scrollToBottom() def clear_selection(self): """Clears all item slections for aesthetical purposes, but only single clicks""" self.list_1.clearSelection() self.list_2.clearSelection() self.list_3.clearSelection() def fileDialog(self): fname = QFileDialog() path = fname.getOpenFileName(self, 'Open file', getcwd(), filter='csv (*.csv);;') if path[0] == '': # failsafe for canceling the dialog return self.curr_file self.curr_file = path[0] self.setWindowTitle(f'{split_name(self.curr_file)}') clear_lists(self.all_lists) lister(file=self.curr_file, target=self.list_1, index=0) lister(file=self.curr_file, target=self.list_2, index=1) lister(file=self.curr_file, target=self.list_3, index=2) status(self.status_bar, self.list_1) self.theme() def save(self, mode=0): self.show_save = False list1_items = items_text(self.list_1) list2_items = items_text(self.list_2) list3_items = items_text(self.list_3) total_dicts = [] for (a, b, c) in zip(list1_items, list2_items, list3_items): # each letter is a column dictionary = {'word_1': a, 'word_2': b, 'notes': c} total_dicts.append(dictionary) if mode == 0: writer(file=self.curr_file, data=total_dicts) status(self.status_bar, self.list_1, ('Saved current changes.')) try: json_template(theme=self.theme_bool, files=[self.curr_file, None, None], window_size=self.geometry().getRect() ) # current size values of the window except: json_template( ) # bug cannot be avoided, even though used setChecked at the beggining elif mode == 1: self.show_save = True writer(file=self.temp_file, data=total_dicts) # avoids stacking and refreshes recent file actions actions = self.fileRecents.actions() for action in actions: self.fileRecents.removeAction(action)
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent=parent) self.threadpool = QThreadPool() self.initUI() self.initList() self.initSearchTab() def initUI(self): self.tabWidget = QTabWidget(self) self.searchEngine = SearchEngine() self.setGeometry(0, 0, 495, 635) self.center() self.setCentralWidget(self.tabWidget) self.status = self.statusBar() self.mainMenu = self.menuBar() exitAct = QAction('&Exit', self) exitAct.setShortcut('Ctrl+Q') exitAct.setStatusTip('Exit application') exitAct.triggered.connect(qApp.quit) exportList = QAction('Export &List',self) exportList.setShortcut('Ctrl+L') exportList.setStatusTip('Export your manga list(backup)') reloadList = QAction('Reload list',self) reloadList.setShortcut('Ctrl+R') reloadList.triggered.connect(self.loadManga) fileMenu = self.mainMenu.addMenu('&File') fileMenu.addAction(exportList) fileMenu.addAction(reloadList) fileMenu.addAction(exitAct) def eventFilter(self, source, event): if (event.type() == QtCore.QEvent.ContextMenu and source is self.searchResults): menu = QMenu() infoAction = QAction('Info',self) infoAction.setStatusTip('Show information about this manga title') infoAction.triggered.connect(self.showMangaInfo) addMangaAction = QAction('Add to list',self) addMangaAction.triggered.connect(self.addToList) menu.addAction(infoAction) menu.addAction(addMangaAction) menu.exec_(event.globalPos()) return True return super(QMainWindow, self).eventFilter(source, event) def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) def loadManga(self): self.mainLayout.clearList() jsonObject = json.loads(open('mangalist.json').read()) for manga in jsonObject['Manga']: manga = Series(self,manga['imagePath'],manga['title']) self.mainLayout.addWidget(manga) def initSearchTab(self): box = QVBoxLayout(self.tabWidget) widget = QWidget(self.tabWidget) self.inputLine = QLineEdit(widget) self.inputLine.returnPressed.connect(self.searchRequest) self.inputLine.setPlaceholderText("Search for a manga title") self.inputLine.setClearButtonEnabled(True) self.searchResults = QListWidget(widget) self.searchResults.setFont(QtGui.QFont('sans-serif', 10, 650)) self.searchResults.installEventFilter(self) box.addWidget(self.inputLine) box.addWidget(self.searchResults) widget.setLayout(box) self.tabWidget.addTab(widget, "Search") def executeThread(self): self.worker = Worker(self.addToList) self.worker.signals.finished.connect(self.threadFinished) self.threadpool.start(self.worker) def threadFinished(self): self.loadManga() def addToList(self): url = self.searchResults.currentItem().data(QtCore.Qt.UserRole) self.statusBar().showMessage("Adding Manga to list.") driver = webdriver.PhantomJS() driver.get(url) soup = bs4.BeautifulSoup(driver.page_source,'lxml') title = soup.select('.tabletitle') imageUrl = soup.select('.img-fluid') image = open('images/'+title[0].text,'wb') image.write(requests.get(imageUrl[2]['src']).content) print(imageUrl[2]) image.close() imagePath = 'images/'+title[0].text description = driver.find_element_by_class_name('sContent') item = {"title": "#","description":"#","imagePath":"#"} item["title"] =title[0].text item["description"]=description.text item["imagePath"]=imagePath config = json.loads(open('mangalist.json').read()) config["Manga"].append(item) with open('mangalist.json','w') as f: f.write(json.dumps(config,indent=4)) f.close() self.statusBar().showMessage("Manga added.",0.5) self.loadManga() def showMangaInfo(self): print(self.searchResults.currentItem().text()) def searchRequest(self): resultList = self.searchEngine.search(self.inputLine.text()) self.inputLine.clear() self.searchResults.clear() for result in resultList: item = QListWidgetItem(result['title']) item.setData(QtCore.Qt.UserRole,result['url']) self.searchResults.addItem(item) def initList(self): self.mainArea = QScrollArea(self) self.mainArea.setWidgetResizable(True) mangaWidget = QWidget(self.mainArea) self.mainLayout = FlowLayout(mangaWidget) self.loadManga() self.mainArea.setWidget(mangaWidget) self.tabWidget.addTab(self.mainArea, "MangaList")