class MP3TagManipulator(QWidget): last_directory = '.' song = None def __init__(self): super().__init__() self.title = 'MP3TagManipulator v1.0' self.top = 100 self.left = 100 self.height = 400 self.width = 640 self.init_ui() def init_ui(self): self.setWindowTitle(self.title) self.setGeometry(self.top, self.left, self.width, self.height) # layout root of application main_layout = QVBoxLayout(self) self.setLayout(main_layout) # to set the model of tree model = QFileSystemModel() model.setRootPath("/home/denison/Downloads/music") model.setNameFilters(['*.mp3', '*.m4a', '*.flac']) model.setNameFilterDisables(False) self.tree = QTreeView() self.tree.setModel(model) self.tree.setAnimated(True) self.tree.setColumnWidth(0, 500) file_layout = QHBoxLayout() label_file = QLabel('file/directory') text_file = QLineEdit() btn_load = QPushButton('load') file_layout.addWidget(label_file) file_layout.addWidget(text_file) file_layout.addWidget(btn_load) grid_info_layout = QGridLayout() # strings to labels self.labels = [ 'ARTIST', 'ALBUMARTIST', 'ALBUM', 'TITLE', 'GENRE', 'DATE' ] # line edits to tags self.text_artist = QLineEdit('ARTIST') self.text_album = QLineEdit('ALBUM') self.text_album_artist = QLineEdit('ALBUMARTIST') self.text_title = QLineEdit('TITLE') self.text_genre = QLineEdit('GENRE') self.text_date = QLineEdit('DATE') self.text_tags = [ self.text_artist, self.text_album_artist, self.text_album, self.text_title, self.text_genre, self.text_date ] for text in self.text_tags: text.setEnabled(False) #text.textChanged.connect(self.enable_save) # labels for label, i in zip(self.labels, range(6)): grid_info_layout.addWidget(QLabel(label), i, 0) # cb_artist = QCheckBox() # cb_album_artist = QCheckBox() # cb_album = QCheckBox() # cb_title = QCheckBox() # cb_genre = QCheckBox() # cb_date = QCheckBox() # self.checkboxes = [ # cb_artist, cb_album_artist, cb_album, # cb_title, cb_genre, cb_date # ] # for cb in self.checkboxes: # cb.setText('editar') # cb_artist.stateChanged.connect(lambda: self.enable_tag_edit(self.text_artist)) # cb_album_artist.stateChanged.connect(lambda: self.enable_tag_edit(self.text_album_artist)) # cb_album.stateChanged.connect(lambda: self.enable_tag_edit(self.text_album)) # cb_title.stateChanged.connect(lambda: self.enable_tag_edit(self.text_title)) # cb_genre.stateChanged.connect(lambda: self.enable_tag_edit(self.text_genre)) # cb_date.stateChanged.connect(lambda: self.enable_tag_edit(self.text_date)) for i, text in zip(range(6), self.text_tags): grid_info_layout.addWidget(text, i, 1) # for cb, i in zip(self.checkboxes, range(6)) : # grid_info_layout.addWidget(cb, i, 2) action_layout = QHBoxLayout() btn_exit = QPushButton('Exit') self.btn_save = QPushButton('save changes') self.btn_save.setDisabled(True) action_layout.addWidget(btn_exit) action_layout.addWidget(self.btn_save) #main_layout.addLayout(file_layout) main_layout.addWidget(self.tree) main_layout.addLayout(grid_info_layout) main_layout.addLayout(action_layout) btn_load.clicked.connect(self.open_file) btn_exit.clicked.connect(self.close_application) self.btn_save.clicked.connect(self.edit_tags) self.tree.doubleClicked.connect(self.get_selected_file) self.show() def enable_edit_text(self): if self.song: for t in self.text_tags: t.setEnabled(True) self.enable_save() else: for t in self.text_tags: t.setEnabled(False) def enable_save(self): self.btn_save.setEnabled(True) def close_application(self): if self.song: self.song.close() print('vazando...;-)') self.close() def enable_tag_edit(self, txt_edit): txt_edit.setEnabled(not txt_edit.isEnabled()) for edit in self.text_tags: edit.textChanged.connect(self.enable_save) print('executou self.enable_tag_edit()') print('não sei o q tá acontecendo :(') def get_selected_file(self): print('executou self.get_selected_file()') selected = self.tree.selectedIndexes()[0] print(selected.model().filePath(selected)) self.song = taglib.File(selected.model().filePath(selected)) self.enable_edit_text() self.load_song_info(self.song) return self.song def edit_tags(self): print("não tá funcionando 8'-(") self.song.tags['ARTIST'] = self.text_artist.text() self.song.tags['ALBUMARTIST'] = self.text_album_artist.text() self.song.tags['ALBUM'] = self.text_album.text() self.song.tags['TITLE'] = self.text_title.text() self.song.tags['GENRE'] = self.text_genre.text() self.song.tags['DATE'] = self.text_date.text() self.song.save() print(self.song.tags) self.btn_save.setDisabled(True) self.song.close() def open_file(self): print('*** quase lá *** ') dialog = QFileDialog(self) dialog.setViewMode(QFileDialog.Detail) file = dialog.getOpenFileName(self, 'load...', self.last_directory, 'songs (*.mp3 *.m4a *.flac *.wma)') song = taglib.File(file[0]) # print(song.tags) self.show_song_info(song) song.close() def load_song_info(self, song): print('executou self.load_song_info()') for t, tag in zip(self.text_tags, self.labels): try: t.setText(song.tags[tag][0]) except KeyError: t.setText('none')
def create_ui(self): """Setup main UI elements, dock widgets, UI-related elements, etc. """ log.debug('Loading UI') # Undo Stack self.undo_stack = QUndoStack(self) self.undo_stack.setUndoLimit(100) # Object navigation history self.obj_history = deque([], config.MAX_OBJ_HISTORY) app = QApplication.instance() base_font = QFont() base_font.fromString(self.prefs['base_font']) app.setFont(base_font) # Object class table widget # classTable = QTableView(self) classTable = classtable.TableView(self) classTable.setObjectName("classTable") classTable.setAlternatingRowColors(True) classTable.setFrameShape(QFrame.StyledPanel) classTable_font = QFont() classTable_font.fromString(self.prefs['class_table_font']) classTable.setFont(classTable_font) fm = classTable.fontMetrics() classTable.setWordWrap(True) classTable.setEditTriggers(QAbstractItemView.EditKeyPressed | QAbstractItemView.DoubleClicked | QAbstractItemView.AnyKeyPressed | QAbstractItemView.SelectedClicked) # classTable.horizontalHeader().setMovable(True) # classTable.verticalHeader().setMovable(False) classTable.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) classTable.verticalHeader().setSectionResizeMode(QHeaderView.Interactive) classTable.horizontalHeader().setDefaultSectionSize(self.prefs['default_column_width']) classTable.verticalHeader().setDefaultSectionSize(fm.height() + 0) classTable.setSelectionMode(QAbstractItemView.ExtendedSelection) classTable.setContextMenuPolicy(Qt.CustomContextMenu) classTable.customContextMenuRequested.connect(self.custom_table_context_menu) # Create table model and proxy layers for transposing and filtering self.classTableModel = classtable.IDFObjectTableModel(classTable) self.transposeableModel = classtable.TransposeProxyModel(self.classTableModel) self.transposeableModel.setSourceModel(self.classTableModel) self.sortableModel = classtable.SortFilterProxyModel(self.transposeableModel) self.sortableModel.setSourceModel(self.transposeableModel) # Assign model to table (enable sorting FIRST) # table.setSortingEnabled(True) # Disable for now, CRUD actions won't work! classTable.setModel(self.sortableModel) # Connect some signals selection_model = classTable.selectionModel() selection_model.selectionChanged.connect(self.table_selection_changed) scroll_bar = classTable.verticalScrollBar() scroll_bar.valueChanged.connect(self.scroll_changed) # These are currently broken # classTable.horizontalHeader().sectionMoved.connect(self.moveObject) # classTable.verticalHeader().sectionMoved.connect(self.moveObject) # Object class tree widget classTreeDockWidget = QDockWidget("Object Classes and Counts", self) classTreeDockWidget.setObjectName("classTreeDockWidget") classTreeDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) classTree = QTreeView(classTreeDockWidget) classTree.setUniformRowHeights(True) classTree.setAllColumnsShowFocus(True) classTree.setRootIsDecorated(False) classTree.setExpandsOnDoubleClick(True) classTree.setIndentation(15) classTree.setAnimated(True) classTree_font = QFont() classTree_font.fromString(self.prefs['class_tree_font']) classTree.setFont(classTree_font) classTree.setAlternatingRowColors(True) classTree.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) palette = classTree.palette() palette.setColor(QPalette.Highlight, Qt.darkCyan) classTree.setPalette(palette) class_tree_window = QWidget(classTreeDockWidget) class_tree_dock_layout_v = QVBoxLayout() class_tree_dock_layout_h = QHBoxLayout() class_tree_dock_layout_v.setContentsMargins(0, 8, 0, 0) class_tree_dock_layout_h.setContentsMargins(0, 0, 0, 0) class_tree_filter_edit = QLineEdit(classTreeDockWidget) class_tree_filter_edit.setPlaceholderText("Filter Classes") class_tree_filter_edit.textChanged.connect(self.treeFilterRegExpChanged) class_tree_filter_cancel = QPushButton("Clear", classTreeDockWidget) class_tree_filter_cancel.setMaximumWidth(45) class_tree_filter_cancel.clicked.connect(self.clearTreeFilterClicked) class_tree_dock_layout_h.addWidget(class_tree_filter_edit) class_tree_dock_layout_h.addWidget(class_tree_filter_cancel) class_tree_dock_layout_v.addLayout(class_tree_dock_layout_h) class_tree_dock_layout_v.addWidget(classTree) class_tree_window.setLayout(class_tree_dock_layout_v) classTreeDockWidget.setWidget(class_tree_window) classTreeDockWidget.setContentsMargins(0,0,0,0) # Comments widget commentDockWidget = QDockWidget("Comments", self) commentDockWidget.setObjectName("commentDockWidget") commentDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) commentView = UndoRedoTextEdit(commentDockWidget, self) commentView.setLineWrapMode(QTextEdit.FixedColumnWidth) commentView.setLineWrapColumnOrWidth(499) commentView.setFrameShape(QFrame.StyledPanel) commentView_font = QFont() commentView_font.fromString(self.prefs['comments_font']) commentView.setFont(commentView_font) commentDockWidget.setWidget(commentView) # Info and help widget infoDockWidget = QDockWidget("Info", self) infoDockWidget.setObjectName("infoDockWidget") infoDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) infoView = QTextEdit(infoDockWidget) infoView.setFrameShape(QFrame.StyledPanel) infoView.setReadOnly(True) infoDockWidget.setWidget(infoView) # Node list and jump menu widget refDockWidget = QDockWidget("Field References", self) refDockWidget.setObjectName("refDockWidget") refDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) ref_model = reftree.ReferenceTreeModel(None, refDockWidget) refView = QTreeView(refDockWidget) refView.setModel(ref_model) refView.setUniformRowHeights(True) refView.setRootIsDecorated(False) refView.setIndentation(15) refView.setColumnWidth(0, 160) refView.setFrameShape(QFrame.StyledPanel) refDockWidget.setWidget(refView) refView.doubleClicked.connect(self.ref_tree_double_clicked) # Logging and debugging widget logDockWidget = QDockWidget("Log Viewer", self) logDockWidget.setObjectName("logDockWidget") logDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) logView = QPlainTextEdit(logDockWidget) logView.setLineWrapMode(QPlainTextEdit.NoWrap) logView.setReadOnly(True) logView_font = QFont() logView_font.fromString(self.prefs['base_font']) logView.setFont(logView_font) logView.ensureCursorVisible() logDockWidget.setWidget(logView) # Undo view widget undoDockWidget = QDockWidget("Undo History", self) undoDockWidget.setObjectName("undoDockWidget") undoDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) undoView = QUndoView(self.undo_stack) undoDockWidget.setWidget(undoView) # Define corner docking behaviour self.setDockNestingEnabled(True) self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea) self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) # Assign main widget and dock widgets to QMainWindow self.setCentralWidget(classTable) self.addDockWidget(Qt.LeftDockWidgetArea, classTreeDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, commentDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, infoDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, refDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, logDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, undoDockWidget) # Store widgets for access by other objects self.classTable = classTable self.commentView = commentView self.infoView = infoView self.classTree = classTree self.logView = logView self.undoView = undoView self.refView = refView self.filterTreeBox = class_tree_filter_edit # Store docks for access by other objects self.commentDockWidget = commentDockWidget self.infoDockWidget = infoDockWidget self.classTreeDockWidget = classTreeDockWidget self.logDockWidget = logDockWidget self.undoDockWidget = undoDockWidget self.refDockWidget = refDockWidget # Perform other UI-related initialization tasks self.center() self.setUnifiedTitleAndToolBarOnMac(True) self.setWindowIcon(QIcon(':/images/logo.png')) # Status bar setup self.statusBar().showMessage('Status: Ready') self.unitsLabel = QLabel() self.unitsLabel.setAlignment(Qt.AlignCenter) self.unitsLabel.setMinimumSize(self.unitsLabel.sizeHint()) self.unitsLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar().addPermanentWidget(self.unitsLabel) self.pathLabel = QLabel() self.pathLabel.setAlignment(Qt.AlignCenter) self.pathLabel.setMinimumSize(self.pathLabel.sizeHint()) self.pathLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar().addPermanentWidget(self.pathLabel) self.versionLabel = QLabel() self.versionLabel.setAlignment(Qt.AlignCenter) self.versionLabel.setMinimumSize(self.versionLabel.sizeHint()) self.versionLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar().addPermanentWidget(self.versionLabel) self.progressBarIDF = QProgressBar() self.progressBarIDF.setAlignment(Qt.AlignCenter) self.progressBarIDF.setMaximumWidth(200) self.statusBar().addPermanentWidget(self.progressBarIDF) self.clipboard = QApplication.instance().clipboard() self.obj_clipboard = [] self.setStyleSheet(""" QToolTip { background-color: gray; color: white; border: black solid 1px } # QMenu { # background-color: rgbf(0.949020, 0.945098, 0.941176); # color: rgb(255,255,255); # } # QMenu::item::selected { # background-color: rgbf(0.949020, 0.945098, 0.941176); # } """)
class Dock(QDockWidget): def __init__(self, title, central_widget, parent=None): super().__init__(title, parent=parent) self.central_widget = central_widget self.model = QFileSystemModel() self.model.setRootPath(QDir.currentPath()) self.tree = QTreeView() self.tree.pressed.connect(self.setIndex) self.index_ = None self.db_tree = QTreeView() self.db_tree.setAnimated(True) self.db_model = QStandardItemModel() self.db_model.setHorizontalHeaderLabels(["Database"]) self.db_root = self.db_model.invisibleRootItem() self.dbs = [] def contextMenuEvent(self, event): contextMenu = QMenu(self) deleteFile = contextMenu.addAction("Delete File") action = contextMenu.exec_(self.mapToGlobal(event.pos())) if action == deleteFile: alert = QMessageBox() alert.setWindowTitle("Action Dialog") alert.setText("Are you sure you want to delete this file?") alert.setStandardButtons(QMessageBox.No | QMessageBox.Yes) alert.setDefaultButton(QMessageBox.No) response = alert.exec_() if response == QMessageBox.No: contextMenu.close() else: file_name = self.model.filePath( self.index_).split("storage/")[1] os.remove(self.model.filePath(self.index_)) os.remove( self.model.filePath(self.index_).split("/storage")[0] + "/meta/" + file_name + "_metadata.json") contextMenu.close() def setIndex(self, index): self.index_ = index def tree_init(self): self.tree.setModel(self.model) self.tree.setRootIndex( self.model.index(QDir.currentPath() + "/model/storage")) self.tree.clicked.connect(self.file_clicked) self.setWidget(self.tree) def init_db_tree(self): self.clear_tree() self.db_tree.setModel(self.db_model) self.db_tree.clicked.connect(self.table_clicked) self.setWidget(self.db_tree) self.connected_dbs() def file_clicked(self, index): file_path = self.model.filePath(index) workspace = Workspace(file_path, self.central_widget) self.central_widget.add_tab( workspace, QIcon("view/images/dark/baseline_notes_black_48dp.png"), file_path.split("storage/")[1], ) def connected_dbs(self): if os.path.exists("model/session/connected_dbs") and os.path.getsize( "model/session/connected_dbs") > 0: self.clear_tree() self.dbs = [] with open("model/session/connected_dbs", "rb") as sessions: db_sessions = pickle.load(sessions) for db in db_sessions: connection = mysql.connect( host=db["host"], user=db["user"], password=db["password"], db=db["db"], charset="utf8mb4", cursorclass=mysql.cursors.DictCursor) temp_db = StandardItem(db["db"], 10, is_bold=True) self.dbs.append(db["db"]) try: with connection.cursor() as cursor: cursor.execute("SHOW TABLES") db_tables = cursor.fetchall() for table in db_tables: for key in table.keys(): temp_table = StandardItem(table[key], 9) temp_db.appendRow(temp_table) finally: connection.close() self.db_root.appendRow(temp_db) else: self.clear_tree() self.dbs = [] no_db = StandardItem("No Connected Databases", 10, is_bold=True) self.db_root.appendRow(no_db) def table_clicked(self, val): self.is_db = False if os.path.exists("model/session/connected_dbs") and len( self.dbs) != 0: for db in self.dbs: if db == val.data(): self.is_db = True remove_choice = QMessageBox.question( self.db_tree, "Disconnect Database", f'Do you want to remove "{val.data()}" from connected databases?', QMessageBox.Yes | QMessageBox.No) if remove_choice == QMessageBox.Yes: with open("model/session/connected_dbs", "rb") as sessions: db_sessions = pickle.load(sessions) db_sessions[:] = [ db for db in db_sessions if db.get('db') != val.data() ] for db in db_sessions: if db["index"] != 1: db["index"] -= 1 self.dbs[:] = [ db for db in self.dbs if db != val.data() ] if len(db_sessions) == 0: os.remove("model/session/connected_dbs") else: with open("model/session/connected_dbs", "wb") as sessions: pickle.dump(db_sessions, sessions) QMessageBox.information( self.db_tree, "(㇏(•̀ᵥᵥ•́)ノ)", f'Database "{val.data()}" successfully disconnected.', QMessageBox.Ok) self.connected_dbs() break if remove_choice == QMessageBox.No: break if not self.is_db: user_reply = QMessageBox.question( self.db_tree, "Answer. Thanks.", f'Do you want to open the "{val.data()}" table?', QMessageBox.Open | QMessageBox.Cancel) if user_reply == QMessageBox.Cancel: pass if user_reply == QMessageBox.Open: workspace = DBWorkspace(val.parent().data(), val.data(), self.central_widget) self.central_widget.add_tab( workspace, QIcon("view/images/menubar/database-connect.png"), val.data(), ) def clear_tree(self): self.db_model.clear() self.db_model.setHorizontalHeaderLabels(["Database"]) self.db_root = self.db_model.invisibleRootItem()