def main(args): app = QApplication(args) page = QSplitter() data = Model(1000, 10, page) selections = QItemSelectionModel(data) table = QTableView() table.setModel(data) table.setSelectionModel(selections) table.horizontalHeader().setSectionsMovable(True) table.verticalHeader().setSectionsMovable(True) # Set StaticContents to enable minimal repaints on resizes. table.viewport().setAttribute(Qt.WA_StaticContents) page.addWidget(table) tree = QTreeView() tree.setModel(data) tree.setSelectionModel(selections) tree.setUniformRowHeights(True) tree.header().setStretchLastSection(False) tree.viewport().setAttribute(Qt.WA_StaticContents) # Disable the focus rect to get minimal repaints when scrolling on Mac. tree.setAttribute(Qt.WA_MacShowFocusRect, False) page.addWidget(tree) list = QListView() list.setModel(data) list.setSelectionModel(selections) list.setViewMode(QListView.IconMode) list.setSelectionMode(QAbstractItemView.ExtendedSelection) list.setAlternatingRowColors(False) list.viewport().setAttribute(Qt.WA_StaticContents) list.setAttribute(Qt.WA_MacShowFocusRect, False) page.addWidget(list) page.setWindowIcon(QIcon(images_dir + '/interview.png')) page.setWindowTitle("Interview") page.show() return app.exec_()
def main(args): app = QApplication(args) page = QSplitter() data = Model(1000, 10, page) selections = QItemSelectionModel(data) table = QTableView() table.setModel(data) table.setSelectionModel(selections) table.horizontalHeader().setSectionsMovable(True) table.verticalHeader().setSectionsMovable(True) # Set StaticContents to enable minimal repaints on resizes. table.viewport().setAttribute(Qt.WA_StaticContents) page.addWidget(table) tree = QTreeView() tree.setModel(data) tree.setSelectionModel(selections) tree.setUniformRowHeights(True) tree.header().setStretchLastSection(False) tree.viewport().setAttribute(Qt.WA_StaticContents) # Disable the focus rect to get minimal repaints when scrolling on Mac. tree.setAttribute(Qt.WA_MacShowFocusRect, False) page.addWidget(tree) list = QListView() list.setModel(data) list.setSelectionModel(selections) list.setViewMode(QListView.IconMode) list.setSelectionMode(QAbstractItemView.ExtendedSelection) list.setAlternatingRowColors(False) list.viewport().setAttribute(Qt.WA_StaticContents) list.setAttribute(Qt.WA_MacShowFocusRect, False) page.addWidget(list) page.setWindowIcon(QIcon(images_dir + "/interview.png")) page.setWindowTitle("Interview") page.show() return app.exec_()
def __init__(self): super().__init__() self.setWindowTitle('GIMZoomer') self.root_path = os.path.expanduser('~') model_filter = QDir.Dirs | QDir.NoDotAndDotDot | QDir.NoSymLinks # model = QDirModel() # model = QFileSystemModel() model = CheckableFileSystemModel() model.setRootPath(self.root_path) model.setFilter(model_filter) tree = QTreeView() tree.setModel(model) tree.setRootIndex(model.index(self.root_path)) tree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents) grid = QGridLayout() grid.addWidget(tree, 0, 0, 1, 1) self.setLayout(grid) self.resize(640, 480) self.show()
def __init__(self, colorModel, parent = None): QDockWidget.__init__(self, "&Colors", parent) self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.colorModel = colorModel self.colorPalette = ColorPalette(self) colorList = QTreeView() colorList.header().hide() colorList.setRootIsDecorated(False) colorList.setModel(self.colorModel) # TODO SIGNAL # self.connect(colorList, SIGNAL("activated(QModelIndex)"), # self.editColor) self.backgroundButton = QPushButton("&Background Color") # TODO SIGNAL # self.connect(self.backgroundButton, SIGNAL("clicked()"), self.selectBackground) self._set_background_button_color(colorModel.background) widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(colorList) layout.addWidget(self.backgroundButton) self.setWidget(widget)
class FileBrowserWidget(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.model = QFileSystemModel() self.model.setRootPath('') self.tree = QTreeView() self.tree.setModel(self.model) idx = self.model.index(BASE_DIR) self.tree.setRootIndex(idx) self.tree.setAnimated(False) self.tree.setIndentation(20) self.tree.setSortingEnabled(True) self.tree.hideColumn(2) self.tree.header().resizeSection(0,200) windowLayout = QVBoxLayout() windowLayout.addWidget(self.tree) self.setLayout(windowLayout)
def init_view(self): """ Init the view attached to the history. """ self.init_model() view = QTreeView() view.setHeaderHidden(True) view.setSelectionBehavior(QAbstractItemView.SelectItems) view.setModel(self.model()) view.pressed.connect(self.dealWithPressEvent) view.header().setStretchLastSection(False) self.setView(view)
def __init__(self, parent=None) -> None: QWidget.__init__(self, parent) # Основные компоненты # Вывод варианта моделирования в виде иерархического дерева variant_tree_view = QTreeView() # Не хотим пользователю дать разрешение на его редактирование variant_tree_view.setEditTriggers(QAbstractItemView.NoEditTriggers) # Ссылка на экземпляр класса, управляющего заголовком header = variant_tree_view.header() # Задание режима для изменения размеров секций (автоматически растягивается) header.setSectionResizeMode(QHeaderView.ResizeToContents) # Модель представления данных self.model = QStandardItemModel(self) variant_tree_view.setModel(self.model) # Групбокс с вариантом моделирования group_box = QGroupBox("Получившийся вариант моделирования") group_box_layout = QVBoxLayout(group_box) group_box_layout.addWidget(variant_tree_view) # Контейнер с кнопками назад/далее control_layout = LayoutWithBackAndNextButtons() # Основной контейнер main_layout = QVBoxLayout(self) main_layout.addWidget(group_box) main_layout.addLayout(control_layout) # Для удобного доступа self.save_file_button = control_layout.next_button self.back_button = control_layout.back_button # Изменим надпись на кнопке на подходящую self.save_file_button.setText("Сохранить вариант моделирования")
class FileTree(QWidget): def __init__(self, defaultfolder=r'c:\Zen_Output'): super(QWidget, self).__init__() filter = ['*.czi', '*.ome.tiff', '*ome.tif' '*.tiff' '*.tif'] # define the style for the FileTree via s style sheet self.setStyleSheet(""" QTreeView::item { background-color: rgb(38, 41, 48); font-weight: bold; } QTreeView::item::selected { background-color: rgb(38, 41, 48); color: rgb(0, 255, 0); } QTreeView QHeaderView:section { background-color: rgb(38, 41, 48); color: rgb(255, 255, 255); } """) self.model = QFileSystemModel() self.model.setRootPath(defaultfolder) self.model.setFilter(QtCore.QDir.AllDirs | QDir.Files | QtCore.QDir.NoDotAndDotDot) self.model.setNameFilterDisables(False) self.model.setNameFilters(filter) self.tree = QTreeView() self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(defaultfolder)) self.tree.setAnimated(True) self.tree.setIndentation(20) self.tree.setSortingEnabled(False) header = self.tree.header() header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) windowLayout = QVBoxLayout() windowLayout.addWidget(self.tree) self.setLayout(windowLayout) self.tree.clicked.connect(self.on_treeView_clicked) @pyqtSlot() def on_treeView_clicked(self, index): indexItem = self.model.index(index.row(), 0, index.parent()) filename = self.model.fileName(indexItem) filepath = self.model.filePath(indexItem) # open the file when clicked print('Opening ImageFile : ', filepath) open_image_stack(filepath)
class ReqTreeView(QWidget): def __init__(self): QWidget.__init__(self) self.setLayout(QVBoxLayout()) self.layout().setSpacing(0) self.layout().setContentsMargins(0, 0, 0, 0) self.nodes = {} self.tree_view = QTreeView() self.tree_view.header().close() self.root = QStandardItemModel() self.tree_view.setModel(self.root) self.layout().addWidget(self.tree_view) @pyqtSlot(HTTPRequest) def add_request_item(self, req): path_parts = req.url.geturl(False).split("/") path_parts = path_parts[1:] path_parts = ["/" + p for p in path_parts] path_parts = [req.dest_host] + path_parts if path_parts[0] not in self.nodes: item = PathNodeItem(path_parts[0], path_parts[0]) item.setFlags(item.flags() ^ Qt.ItemIsEditable) self.nodes[path_parts[0]] = item self.root.appendRow(item) else: item = self.nodes[path_parts[0]] item.add_child_path(path_parts[1:]) @pyqtSlot(list) def set_requests(self, reqs): self.clear() for req in reqs: if _include_req(req): self.add_request_item(req) self.tree_view.expandAll() def clear(self): self.nodes = {} self.root = QStandardItemModel() self.tree_view.setModel(self.root)
class RouteTable(QWidget): NO1, FROM, NO2, TO, CLUSTER = range(5) def __init__(self): super().__init__() # Main layout main_layout = QVBoxLayout() # Sub layout self.cluster_combobox = QComboBox() self.table_container = QGroupBox("Result") # Route Table self.table = QTreeView() self.table.setRootIsDecorated(False) self.table.setAlternatingRowColors(True) self.model = QStandardItemModel(0, 5, self) self.model.setHeaderData(self.NO1, Qt.Horizontal, "No") self.model.setHeaderData(self.FROM, Qt.Horizontal, "From") self.model.setHeaderData(self.NO2, Qt.Horizontal, "No") self.model.setHeaderData(self.TO, Qt.Horizontal, "To") self.model.setHeaderData(self.CLUSTER, Qt.Horizontal, "Cluster number") self.table.setModel(self.model) table_header = self.table.header() table_header.resizeSection(0, 25) table_header.resizeSection(1, 190) table_header.resizeSection(2, 25) table_header.resizeSection(3, 190) table_container_layout = QHBoxLayout() table_container_layout.addWidget(self.table) self.table_container.setLayout(table_container_layout) # Add to main layout main_layout.addWidget(self.cluster_combobox) main_layout.addWidget(self.table_container) self.setLayout(main_layout) def addRoute(self, model, from_no, from_route, to_no, to_route, cluster_number): root = self.table.model() n = root.rowCount() model.insertRow(n) model.setData(model.index(n, self.NO1), from_no) model.setData(model.index(n, self.FROM), from_route) model.setData(model.index(n, self.NO2), to_no) model.setData(model.index(n, self.TO), to_route) model.setData(model.index(n, self.CLUSTER), cluster_number) def clearAllRoute(self): root = self.table.model() root.removeRows(0, root.rowCount())
def showVideoInfoDialog(self, outjson): """ Show Video Information Dialog """ view = QTreeView() model = QJsonModel() view.setModel(model) model.loadJsonFromConsole(outjson) self.VideoInfoDialog = QDialog(self) self.VideoInfoDialog.setWindowTitle("Video Information : " + self.fileName) self.VideoInfoDialog.setWindowIcon( QIcon(":/imgFMV/images/video_information.png")) self.verticalLayout = QVBoxLayout(self.VideoInfoDialog) self.verticalLayout.addWidget(view) view.expandAll() view.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.VideoInfoDialog.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) self.VideoInfoDialog.setObjectName("VideoInfoDialog") self.VideoInfoDialog.resize(500, 400) self.VideoInfoDialog.show()
class StacktraceWidget(QWidget): def __init__(self, parent): super().__init__(parent) self.parent = parent self.tree = QTreeView(self) layout = QVBoxLayout(self) layout.addWidget(self.tree) self.model = QStandardItemModel() self.model.setHorizontalHeaderLabels(["Level", "Values"]) self.tree.header().setDefaultSectionSize(180) self.tree.setModel(self.model) def importData(self, data): self.model.setRowCount(0) root = self.model.invisibleRootItem() for index, stack_values in enumerate(data): parent = QStandardItem( str(stack_values.f_code.co_name) + str( inspect.signature( types.FunctionType(stack_values.f_code, {}) ) ) ) parent.setEditable(False) for key, value in stack_values.f_locals.items(): keyWidget = QStandardItem(str(key)) keyWidget.setEditable(False) try: value_as_str = repr(value) except BaseException: value_as_str = "Can't see" valueWidget = ValueWidget( self.parent, value_as_str, str(key), str(index) ) parent.appendRow([keyWidget, valueWidget]) root.appendRow(parent)
class CategoriesWidget(QWidget): def __init__(self, categoriesTreeItems): super().__init__() self.setLayout(QGridLayout()) self.categoriesTreeItems = categoriesTreeItems self.categoriesView = QTreeView() self.categoriesView.header().hide() self.categoriesModel = TreeModel(self.categoriesTreeItems) self.categoriesProxyModel = CategoriesFilterProxy() self.categoriesProxyModel.setSourceModel(self.categoriesModel) self.categoriesView.setModel(self.categoriesProxyModel) self.filterCategoriesEdit = QLineEdit() self.filterCategoriesEdit.setPlaceholderText("Filter categories") self.categoriesProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.filterCategoriesEdit.textEdited.connect( self.categoriesProxyModel.setFilterRegExp) self.layout().addWidget(self.filterCategoriesEdit, 0, 1, 1, 1) self.layout().addWidget(self.categoriesView, 1, 1, 1, 1)
class PYCFScape(QMainWindow): def __init__(self): super().__init__() # We determine where the script is placed, for acessing files we use (such as the icon) self.we_live_in = sys.argv[0] self.we_live_in = os.path.split(self.we_live_in)[0] self.build_interface() self.opened_file = None # stores the filepath self.opened_vpk = None # stores the opened vpk self.internal_directory_understanding = { } # a dictionary version of the paths self.vpk_loaded = False # whether or not we have a vpk file open self.export_paths = [ ] # Paths we want to export when file->export is selected def build_interface(self): self.setWindowTitle("PYCFScape") self.setWindowIcon( QIcon(os.path.join(self.we_live_in, 'res/Icon64.ico'))) self.main_content = QWidget() self.main_content_layout = QVBoxLayout() self.main_content.setLayout(self.main_content_layout) # set up the interface parts self.output_console = QTextEdit() # Will basically just copy stdout self.output_console.setReadOnly(True) sys.stdout = bob_logger(self.output_console, False, sys.stdout) sys.stderr = bob_logger(self.output_console, True, sys.stderr) self.vpk_tree = QTreeView() # Displays the tree of the vpk's content self.vpk_tree_model = QStandardItemModel(self.vpk_tree) self.vpk_tree.setModel(self.vpk_tree_model) self.vpk_tree._mousePressEvent = self.vpk_tree.mousePressEvent # store it so we can call it self.vpk_tree.mousePressEvent = self.vpk_tree_item_clicked self.vpk_tree.setHeaderHidden(True) # We use a QTreeView to also display headers. # We however, still treat it as a list view. self.dir_list = QTreeView( ) # Displays the list of the current vpk's directory's content self.dir_list_model = QStandardItemModel(self.dir_list) self.dir_list.setModel(self.dir_list_model) self.dir_list.doubleClicked.connect(self.dir_list_item_double_clicked) self.dir_list_model.itemChanged.connect(self.dir_list_item_updated) self.dir_list.setContextMenuPolicy(Qt.CustomContextMenu) self.dir_list.customContextMenuRequested.connect( self.dir_list_context_menu) self.dir_list_model.setColumnCount(2) self.dir_list_model.setHorizontalHeaderLabels(["Name", "Type"]) self.dir_list.header().resizeSection(0, 250) # The tool bar - WARNING: MESSY CODE! self.actions_toolbar = self.addToolBar("") # OPEN BUTTON self.open_button = self.actions_toolbar.addAction( QIcon.fromTheme("document-open"), "Open File") self.open_button.triggered.connect(self.open_file) self.actions_toolbar.addSeparator() self.back_button = QToolButton() # GO BACK BUTTON self.back_button.setIcon(QIcon.fromTheme("go-previous")) self.back_button.setDisabled(True) self.actions_toolbar.addWidget(self.back_button) self.forward_button = QToolButton() # GO FORWARD BUTTON self.forward_button.setIcon(QIcon.fromTheme("go-next")) self.forward_button.setDisabled(True) self.actions_toolbar.addWidget(self.forward_button) self.up_button = QToolButton() # GO UP BUTTON self.up_button.setIcon(QIcon.fromTheme("go-up")) self.up_button.setDisabled(True) self.actions_toolbar.addWidget(self.up_button) self.actions_toolbar.addSeparator() # FIND BUTTON self.search_button = self.actions_toolbar.addAction( QIcon.fromTheme("system-search"), "Find in file") self.search_button.setDisabled(True) self.search_button.triggered.connect(self.search_for_file) self.actions_toolbar.addSeparator() # EXPORT BUTTON self.export_button = self.actions_toolbar.addAction( QIcon.fromTheme("extract-archive"), "Export Selection") self.export_button.setDisabled(False) self.export_button.triggered.connect(self.export_selection) self.main_content_layout.addWidget(self.actions_toolbar) # now we want the menubar self.menu_bar = self.menuBar() self.file_menu = self.menu_bar.addMenu("&File") self.edit_menu = self.menu_bar.addMenu("&Edit") self.menu_bar.addSeparator() self.help_menu = self.menu_bar.addMenu("&Help") self.file_menu.addActions([self.open_button]) self.close_button = self.file_menu.addAction( QIcon.fromTheme("document-close"), "Close File" # bit redundant, i actually see no use ) self.close_button.triggered.connect(self.close_vpk) self.file_menu.addSeparator() self.file_menu.addAction(QIcon.fromTheme("application-exit"), "Exit").triggered.connect(self.close) self.edit_menu.addActions([self.search_button]) self.help_menu.addAction(QIcon.fromTheme("help-about"), "About && License").triggered.connect( self.about) # the statusbar self.status_bar = self.statusBar() # set up the splitters # horizontal self.horz_splitter_container = QWidget() self.horz_splitter_container_layout = QVBoxLayout() self.horz_splitter_container.setLayout( self.horz_splitter_container_layout) self.horz_splitter = QSplitter(Qt.Horizontal) self.horz_splitter.addWidget(self.vpk_tree) self.horz_splitter.addWidget(self.dir_list) self.horz_splitter.setSizes([50, 200]) self.horz_splitter_container_layout.addWidget(self.horz_splitter) # vertical self.vert_splitter = QSplitter(Qt.Vertical) self.vert_splitter.addWidget(self.horz_splitter_container) self.vert_splitter.addWidget(self.output_console) self.vert_splitter.setSizes([200, 50]) self.main_content_layout.addWidget(self.vert_splitter) self.setCentralWidget(self.main_content) self.show() ## # Update Functions ## def update_console(self, text, is_stdout): colour = Qt.Red if not is_stdout else Qt.black self.output_console.setTextColor(colour) self.output_console.moveCursor(QTextCursor.End) self.output_console.insertPlainText(text) def update_interface(self): # update the tree view self.update_vpk_tree() # update the list view self.update_dir_list() self.search_button.setDisabled(not self.vpk_loaded) def update_vpk_tree(self): self.vpk_tree_model.removeRows(0, self.vpk_tree_model.rowCount()) if self.opened_vpk: self.tree_magic(self.internal_directory_understanding) def update_dir_list(self): self.dir_list_model.removeRows(0, self.dir_list_model.rowCount( )) # remove anyway (for instances such as opening a new file) selected = self.vpk_tree.selectedIndexes() if not selected: return selected = selected[0] selected_item = self.vpk_tree_model.itemFromIndex(selected) if not selected_item: return if not selected_item.is_dir: return path = selected_item.path understanding = self.get_understanding_from( self.internal_directory_understanding, path) self.list_magic(understanding, path) ## # Events ## def vpk_tree_item_clicked(self, event): self.vpk_tree._mousePressEvent(event) index = self.vpk_tree.indexAt(event.pos()) # We can rather safely assume any items in the vpk tree will have OUR special attributes if index.isValid(): item = self.vpk_tree_model.itemFromIndex(index) print("selected", item.path) if item.is_dir: self.update_dir_list() def dir_list_item_double_clicked(self, index): item = self.dir_list_model.itemFromIndex(index) if not item.column() == 0: return print("double clicked", item.path) if item.is_dir: print("is dir") # this is probably a REALLY **REALLY** awful way of doing this, but you're welcome to PR a better way. :) index_in_tree = self.find_in_model(self.vpk_tree_model, item.path) if index_in_tree.isValid(): self.vpk_tree.setCurrentIndex(index_in_tree) self.update_dir_list() else: self.status_bar.showMessage(MSG_EXPORT) # we clearly wanna export the file and open it bits = self.opened_vpk[item.path[1:]].read() path = compat.write_to_temp(bits, os.path.split(item.path)[1]) print("Wrote to", path) compat.tell_os_open(path) self.status_bar.clearMessage() def dir_list_item_updated(self, item): if item.checkState() == Qt.Checked: if not item.is_dir: self.export_paths.append(item.path) else: index_in_tree = self.find_in_model(self.vpk_tree_model, item.path) if index_in_tree.isValid(): paths = self.recursively_get_paths_from_dir_index_item( index_in_tree, self.vpk_tree_model) self.export_paths += paths elif item.checkState() == Qt.Unchecked: if not item.is_dir: if item.path in self.export_paths: self.export_paths.remove(item.path) else: index_in_tree = self.find_in_model(self.vpk_tree_model, item.path) if index_in_tree.isValid(): paths = self.recursively_get_paths_from_dir_index_item( index_in_tree, self.vpk_tree_model) for path in paths: if path in self.export_paths: self.export_paths.remove(path) ## # The next 3 functions are the original pathtodir but merged with the program ## def nested_dict(self): return defaultdict(self.nested_dict) def nested_dict_to_regular(self, d): if not isinstance(d, defaultdict): return d return {k: self.nested_dict_to_regular(v) for k, v in d.items()} def understand_directories(self, list_of_paths): use_dict = defaultdict(self.nested_dict) for path in list_of_paths: parts = path.split('/') if parts: marcher = use_dict for key in parts[:-1]: marcher = marcher[key] marcher[parts[-1]] = parts[-1] return dict(use_dict) def get_understanding_from(self, understanding, path): keys = path.split('/') if keys[0] == '': keys = keys[1:] if keys: marcher = understanding for key in keys: marcher = marcher[key] # we can now assume marcher is the understanding we want return marcher ## # Utility ## def tree_magic(self, dict_of_things, parent=None, root=''): if not parent: parent = self.vpk_tree_model # Stack overflow 14478170 for thing in sorted(dict_of_things, key=lambda f: os.path.splitext(f)): path = root + '/{}'.format(thing) thing_item = QStandardItem() thing_item.setText(thing) thing_item.setEditable(False) thing_item.path = path thing_item.is_dir = False icon, _ = self.get_info_from_path(path) thing_item.setIcon(icon) if isinstance(dict_of_things[thing], dict): thing_item.setIcon(QIcon.fromTheme("folder")) self.tree_magic(dict_of_things[thing], thing_item, path) thing_item.is_dir = True parent.appendRow(thing_item) def list_magic(self, dict_of_things, root=''): # like tree_magic but operates on dir_list for thing in sorted(dict_of_things, key=lambda f: os.path.splitext(f)): path = root + '/{}'.format(thing) thing_item = QStandardItem() thing_item.setText(thing) thing_item.setEditable(False) thing_item.setCheckable(True) thing_item_type = QStandardItem( ) # This doesn't actually do anything but convey more information to the user thing_item_type.setEditable(False) if path in self.export_paths: thing_item.setCheckState(Qt.Checked) thing_item.path = path thing_item.is_dir = False icon, desc = self.get_info_from_path(path) thing_item.setIcon(icon) thing_item_type.setText(desc) if isinstance(dict_of_things[thing], dict): thing_item.setIcon(QIcon.fromTheme("folder")) thing_item.is_dir = True thing_item_type.setText("Directory") self.dir_list_model.appendRow([thing_item, thing_item_type]) def get_info_from_path(self, path): # returns the icon AND description string icon = None desc = None # first we test against mimetype # probably bad code, but it works! thing_mimetype = mimetypes.guess_type(path)[0] if thing_mimetype: if thing_mimetype[:6] == "audio/": icon = QIcon.fromTheme("audio-x-generic") elif thing_mimetype[:12] == "application/": icon = QIcon.fromTheme("application-x-generic") elif thing_mimetype[:5] == "text/": icon = QIcon.fromTheme("text-x-generic") elif thing_mimetype[:6] == "image/": icon = QIcon.fromTheme("image-x-generic") elif thing_mimetype[:6] == "video/": icon = QIcon.fromTheme("video-x-generic") desc = thing_mimetype # well ok, maybe that didn't work, let's test the filepath ourselves. file_ext = os.path.splitext(path)[1] if file_ext: if file_ext in [".vtf"]: icon = QIcon.fromTheme("image-x-generic") desc = "Valve Texture File" elif file_ext in [".vmt"]: icon = QIcon.fromTheme("text-x-generic") desc = "Valve Material File" elif file_ext in [ ".pcf" ]: # we can safely assume they are not fonts in this context, but rather icon = QIcon.fromTheme("text-x-script") desc = "Valve DMX Implementation" # TODO: is there a better name elif file_ext in [".bsp"]: icon = QIcon.fromTheme("text-x-generic") desc = "Binary Space Partition" elif file_ext in [".res"]: icon = QIcon.fromTheme("text-x-generic") desc = "Valve Key Value" elif file_ext in [".vcd"]: icon = QIcon.fromTheme("text-x-generic") desc = "Valve Choreography Data" if not icon: # If all else fails, display SOMETHING icon = QIcon.fromTheme("text-x-generic") if not desc: desc = "File" return icon, desc def find_in_model(self, model: QStandardItemModel, path): for i in range(0, model.rowCount()): index_in_tree = model.index(i, 0) if model.itemFromIndex(index_in_tree).path == path: return index_in_tree if model.itemFromIndex(index_in_tree).is_dir: index_in_tree = self.find_in_model_parent(model, path, parent=index_in_tree) if not index_in_tree.isValid(): continue if model.itemFromIndex(index_in_tree).path == path: return index_in_tree def find_in_model_parent(self, model: QStandardItemModel, path, parent): for i in range(0, model.rowCount(parent)): index_in_tree = model.index(i, 0, parent) if model.itemFromIndex(index_in_tree).path == path: return index_in_tree if model.itemFromIndex(index_in_tree).is_dir: index_in_tree = self.find_in_model_parent(model, path, parent=index_in_tree) if not index_in_tree.isValid(): continue if model.itemFromIndex(index_in_tree).path == path: return index_in_tree return QModelIndex() def export_file(self, path, out_dir): filepath = os.path.split(path)[0] if not os.path.isdir('{}{}'.format(out_dir, filepath)): os.makedirs('{}{}'.format(out_dir, filepath)) print("Attempting to export to", "{}{}".format(out_dir, filepath), "from", path[1:], "in the vpk") outcontents = self.opened_vpk.get_file(path[1:]).read() outfile = open('{}{}'.format(out_dir, path), 'wb') outfile.write(outcontents) outfile.close() def recursively_get_paths_from_dir_index_item(self, index_in_tree, model): paths = [] for i in range( self.vpk_tree_model.itemFromIndex(index_in_tree).rowCount()): index = self.vpk_tree_model.index(i, 0, index_in_tree) index_item = self.vpk_tree_model.itemFromIndex(index) if not index_item.is_dir: paths.append(index_item.path) else: paths += self.recursively_get_paths_from_dir_index_item( index, model) return paths def close_vpk(self): # We trash everything! self.vpk_loaded = False self.opened_file = None self.opened_vpk = {} self.export_paths = [] self.internal_directory_understanding = {} self.status_bar.showMessage(MSG_UPDATE_UI) self.update_interface() self.status_bar.clearMessage() def open_vpk(self, vpk_path): if self.vpk_loaded: # if we already have a file open, close it. self.close_vpk() self.status_bar.showMessage(MSG_OPEN_VPK) if not os.path.exists(vpk_path): print( "Attempted to open {}, which doesn't exist.".format(vpk_path)) return self.opened_file = vpk_path try: self.opened_vpk = vpk.open(vpk_path) except Exception as e: print("Ran into an error from the VPK Library.") sys.stdout.write(str(e)) self.error_box(str(e)) return self.vpk_loaded = True self.status_bar.showMessage(MSG_UNDERSTAND_VPK) # Now we attempt to understand the vpk self.internal_directory_understanding = self.understand_directories( self.opened_vpk) self.status_bar.showMessage(MSG_UPDATE_UI) self.update_interface() self.status_bar.clearMessage() ## # Dialogs ## def open_dialog(self): fn = QFileDialog.getOpenFileName(None, "Open Package", str(pathlib.Path.home()), filter=("Valve Pak Files (*.vpk)")) return fn def open_dir_dialog(self, title="Open Directory"): fn = QFileDialog.getExistingDirectory(None, title, str(pathlib.Path.home())) return fn def error_box(self, text="...", title="Error"): box = QMessageBox() box.setIcon(QMessageBox.Critical) box.setText(text) box.setWindowTitle(title) box.setStandardButtons(QMessageBox.Ok) return box.exec() def info_box(self, text="...", title="Info"): box = QMessageBox() box.setIcon(QMessageBox.Information) box.setText(text) box.setWindowTitle(title) box.setStandardButtons(QMessageBox.Ok) return box.exec() def dir_list_context_menu(self, event): menu = QMenu(self) selected = self.dir_list.selectedIndexes() if not selected: return selected = selected[0] selected_item = self.dir_list_model.itemFromIndex(selected) path = selected_item.path extract = menu.addAction(QIcon.fromTheme("extract-archive"), "Extract") validate = menu.addAction(QIcon.fromTheme("view-certificate"), "Validate") # TODO: better validation icon menu.addSeparator() gotodirectory = menu.addAction(QIcon.fromTheme("folder"), "Go To Directory") menu.addSeparator() properties = menu.addAction(QIcon.fromTheme("settings-configure"), "Properties") extract.setDisabled(selected_item.is_dir) validate.setDisabled(selected_item.is_dir) action = menu.exec_(self.dir_list.mapToGlobal(event)) if action == extract: self.export_selected_file(path) elif action == validate: self.validate_file(path) elif action in [gotodirectory, properties]: self.info_box( "I'm not sure what this does in the original GCFScape.\nIf you know, please make an issue on github!" ) def about(self): box = QMessageBox() box.setWindowTitle( "About PYCFScape") # TODO: what do we version and how box.setText("""PYCFScape Version 0 MIT LICENSE V1.00 OR LATER Python {} QT {} AUTHORS (Current Version): ACBob - https://acbob.gitlab.io Project Homepage https://github.com/acbob/pycfscape""".format(sys.version, QT_VERSION_STR)) box.setIcon(QMessageBox.Information) box.exec() ## # Actions ## def open_file(self): self.status_bar.showMessage(MSG_USER_WAIT) fn = self.open_dialog()[0] if not fn: self.status_bar.clearMessage() return self.open_vpk(fn) def search_for_file(self, event): print(event) def export_selection(self): if not self.export_paths: self.info_box( "You can't export nothing!\n(Please select some items to export.)" ) return self.status_bar.showMessage(MSG_USER_WAIT) output_dir = self.open_dir_dialog("Export to...") if output_dir: self.status_bar.showMessage(MSG_EXPORT) print("attempting export to", output_dir) for file in self.export_paths: self.export_file(file, output_dir) self.status_bar.clearMessage() def export_selected_file(self, file): self.status_bar.showMessage(MSG_USER_WAIT) output_dir = self.open_dir_dialog("Export to...") if output_dir: self.status_bar.showMessage(MSG_EXPORT) print("attempting export to", output_dir) self.export_file(file, output_dir) self.status_bar.clearMessage() def validate_file(self, file): filetoverify = self.opened_vpk.get_file(file[1:]) if filetoverify: verified = filetoverify.verify() if verified: self.info_box("{} is a perfectly healthy file.".format(file), "All's good.") else: self.error_box("{} is not valid!".format(file), "Uh oh.") else: print("What? file doesn't exist? HOW IS THIS MAN")
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self._app = QApplication.instance() self._lineEdit = None self.create_menu() self._completer = TreeModelCompleter(None, self) self._completer.setModel(self.model_from_file("./treemodel.txt")) self._completer.set_separator(".") self._completer.highlighted[QModelIndex].connect(self.highlight) central_widget = QWidget() model_label = QLabel() model_label.setText("Tree Model<br>(Double click items to edit)") mode_label = QLabel() mode_label.setText("Completion Mode") self._mode_combo = QComboBox() self._mode_combo.addItem(tr("Inline")) self._mode_combo.addItem(tr("Filtered Popup")) self._mode_combo.addItem(tr("Unfiltered Popup")) self._mode_combo.setCurrentIndex(1) case_label = QLabel() case_label.setText(tr("Case Sensitivity")) self._case_combo = QComboBox() self._case_combo.addItem(tr("Case Insensitive")) self._case_combo.addItem(tr("Case Sensitive")) self._case_combo.setCurrentIndex(0) separator_label = QLabel() separator_label.setText(tr("Tree Separator")) separator_line_edit = QLineEdit() separator_line_edit.setText(self._completer.separator()) separator_line_edit.textChanged.connect(self._completer.set_separator) wrap_check_box = QCheckBox() wrap_check_box.setText(tr("Wrap around completions")) wrap_check_box.setChecked(self._completer.wrapAround()) wrap_check_box.clicked.connect(self._completer.setWrapAround) self._contents_label = QLabel() self._contents_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) separator_line_edit.textChanged.connect(self.update_contents_label) self._tree_view = QTreeView() self._tree_view.setModel(self._completer.model()) self._tree_view.header().hide() self._tree_view.expandAll() self._mode_combo.activated.connect(self.change_mode) self._case_combo.activated.connect(self.change_case) self._line_edit = QLineEdit() self._line_edit.setCompleter(self._completer) layout = QGridLayout() layout.addWidget(model_label, 0, 0), layout.addWidget(self._tree_view, 0, 1) layout.addWidget(mode_label, 1, 0), layout.addWidget(self._mode_combo, 1, 1) layout.addWidget(case_label, 2, 0), layout.addWidget(self._case_combo, 2, 1) layout.addWidget(separator_label, 3, 0), layout.addWidget(separator_line_edit, 3, 1) layout.addWidget(wrap_check_box, 4, 0) layout.addWidget(self._contents_label, 5, 0, 1, 2) layout.addWidget(self._line_edit, 6, 0, 1, 2) central_widget.setLayout(layout) self.setCentralWidget(central_widget) self.change_case(self._case_combo.currentIndex()) self.change_mode(self._mode_combo.currentIndex()) self.setWindowTitle(tr("Tree Model Completer")) self._line_edit.setFocus() def create_menu(self): exit_action = QAction(tr("Exit"), self) about_act = QAction(tr("About"), self) about_qt_act = QAction(tr("About Qt"), self) exit_action.triggered.connect(QApplication.instance().quit) about_act.triggered.connect(self.about) about_qt_act.triggered.connect(QApplication.instance().aboutQt) file_menu = self.menuBar().addMenu(tr("File")) file_menu.addAction(exit_action) help_menu = self.menuBar().addMenu(tr("About")) help_menu.addAction(about_act) help_menu.addAction(about_qt_act) def change_mode(self, index): modes = (QCompleter.InlineCompletion, QCompleter.PopupCompletion, QCompleter.UnfilteredPopupCompletion) self._completer.setCompletionMode(modes[index]) def model_from_file(self, file_name): # file = QFile(file_name) # if not file.open(QFile.ReadOnly): # return QStringListModel(self._completer) QApplication.instance().setOverrideCursor(QCursor(Qt.WaitCursor)) model = QStandardItemModel(self._completer) parents = [model.invisibleRootItem()] with open(file_name) as file: pat = re.compile("^\\s+") for line in file: if not line: continue trimmed_line = line.strip() if not trimmed_line: continue match = pat.match(line) if not match: level = 0 else: length = match.end() - match.start() if line.startswith("\t"): level = length else: level = length // 4 while len(parents) < level + 2: parents.append(None) item = QStandardItem() item.setText(trimmed_line) parents[level].appendRow(item) parents[level + 1] = item QApplication.instance().restoreOverrideCursor() return model @pyqtSlot(QModelIndex) def highlight(self, index): proxy = self._completer.completionModel() source_index = proxy.mapToSource(index) self._tree_view.selectionModel().select( source_index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self._tree_view.scrollTo(source_index) def about(self): QMessageBox.about( self, tr("About"), tr("This example demonstrates how to use a QCompleter with a custom tree datamodel." )) def change_case(self, cs): self._completer.setCaseSensitivity( Qt.CaseSensitive if cs else Qt.CaseInsensitive) def update_contents_label(self, sep): self._contents_label.setText( "Type path from datamodel above with items at each level separated by a '%s'" % sep)
class RegisterViewWidget(WidgetBase): def __init__(self, parent: QWidget): super(RegisterViewWidget, self).__init__(parent) self._registers = [] self._running_task: asyncio.Task = None self._visibility_selector = QComboBox(self) self._visibility_selector.addItem("Show all registers", lambda _: True) self._visibility_selector.addItem("Only configuration parameters", lambda r: r.mutable and r.persistent) # noinspection PyUnresolvedReferences self._visibility_selector.currentIndexChanged.connect( lambda _: self._on_visibility_changed()) self._reset_selected_button = make_button( self, "Reset selected", icon_name="clear-symbol", tool_tip=f"Reset the currently selected registers to their default " f"values. The restored values will be committed " f"immediately. This function is available only if a " f"default value is defined. [{RESET_SELECTED_SHORTCUT}]", on_clicked=self._do_reset_selected, ) self._reset_all_button = make_button( self, "Reset all", icon_name="skull-crossbones", tool_tip=f"Reset the all registers to their default " f"values. The restored values will be committed " f"immediately.", on_clicked=self._do_reset_all, ) self._read_selected_button = make_button( self, "Read selected", icon_name="process", tool_tip=f"Read the currently selected registers only " f"[{READ_SELECTED_SHORTCUT}]", on_clicked=self._do_read_selected, ) self._read_all_button = make_button( self, "Read all", icon_name="process-plus", tool_tip="Read all registers from the device", on_clicked=self._do_read_all, ) self._export_button = make_button( self, "Export", icon_name="export", tool_tip="Export configuration parameters", on_clicked=self._do_export, ) self._import_button = make_button( self, "Import", icon_name="import", tool_tip="Import configuration parameters", on_clicked=self._do_import, ) self._expand_all_button = make_button( self, "", icon_name="expand-arrow", tool_tip="Expand all namespaces", on_clicked=lambda: self._tree.expandAll(), ) self._collapse_all_button = make_button( self, "", icon_name="collapse-arrow", tool_tip="Collapse all namespaces", on_clicked=lambda: self._tree.collapseAll(), ) self._status_display = QLabel(self) self._status_display.setWordWrap(True) self._reset_selected_button.setEnabled(False) self._reset_all_button.setEnabled(False) self._read_selected_button.setEnabled(False) self._read_all_button.setEnabled(False) self._export_button.setEnabled(False) self._import_button.setEnabled(False) self._tree = QTreeView(self) self._tree.setVerticalScrollMode(QTreeView.ScrollPerPixel) self._tree.setHorizontalScrollMode(QTreeView.ScrollPerPixel) self._tree.setAnimated(True) self._tree.setSelectionMode(QAbstractItemView.ExtendedSelection) self._tree.setAlternatingRowColors(True) self._tree.setContextMenuPolicy(Qt.ActionsContextMenu) # Not sure about this one. This hardcoded value may look bad on some platforms. self._tree.setIndentation(20) def add_action( callback: typing.Callable[[], None], icon_name: str, name: str, shortcut: typing.Optional[str] = None, ): action = QAction(get_icon(icon_name), name, self) # noinspection PyUnresolvedReferences action.triggered.connect(callback) if shortcut: action.setShortcut(shortcut) action.setAutoRepeat(False) try: action.setShortcutVisibleInContextMenu(True) except AttributeError: pass # This feature is not available in PyQt before 5.10 self._tree.addAction(action) add_action(self._do_read_all, "process-plus", "Read all registers") add_action( self._do_read_selected, "process", "Read selected registers", READ_SELECTED_SHORTCUT, ) add_action( self._do_reset_selected, "clear-symbol", "Reset selected to default", RESET_SELECTED_SHORTCUT, ) self._tree.setItemDelegateForColumn( int(Model.ColumnIndices.VALUE), EditorDelegate(self._tree, self._display_status), ) # It doesn't seem to be explicitly documented, but it seems to be necessary to select either top or bottom # decoration position in order to be able to use center alignment. Left or right positions do not work here. self._tree.setItemDelegateForColumn( int(Model.ColumnIndices.FLAGS), StyleOptionModifyingDelegate( self._tree, decoration_position=QStyleOptionViewItem.Top, # Important decoration_alignment=Qt.AlignCenter, ), ) header: QHeaderView = self._tree.header() header.setSectionResizeMode(QHeaderView.ResizeToContents) header.setStretchLastSection( False) # Horizontal scroll bar doesn't work if this is enabled buttons_layout = QGridLayout() buttons_layout.addWidget(self._read_selected_button, 0, 0) buttons_layout.addWidget(self._reset_selected_button, 0, 2) buttons_layout.addWidget(self._read_all_button, 1, 0) buttons_layout.addWidget(self._reset_all_button, 1, 2) buttons_layout.addWidget(self._import_button, 2, 0) buttons_layout.addWidget(self._export_button, 2, 2) for col in range(3): buttons_layout.setColumnStretch(col, 1) layout = lay_out_vertically( (self._tree, 1), buttons_layout, lay_out_horizontally( self._visibility_selector, (None, 1), self._expand_all_button, self._collapse_all_button, ), self._status_display, ) self.setLayout(layout) def reset(self): self.setup([]) def setup(self, registers: typing.Iterable[Register]): self._registers = list(registers) self._on_visibility_changed() def _replace_model( self, register_visibility_predicate: typing.Callable[[Register], bool]): # Cancel all operations that might be pending on the old model self._cancel_task() old_model = self._tree.model() # Configure the new model filtered_registers = list( filter(register_visibility_predicate, self._registers)) # It is important to set the Tree widget as the parent in order to let the widget take ownership new_model = Model(self._tree, filtered_registers) _logger.info("New model %r", new_model) self._tree.setModel(new_model) # The selection model is implicitly replaced when we replace the model, so it has to be reconfigured self._tree.selectionModel().selectionChanged.connect( lambda *_: self._on_selection_changed()) # TODO: Something fishy is going on. Something keeps the old model alive when we're replacing it. # We could call deleteLater() on it, but it seems dangerous, because if that something ever decided # to refer to that dead model later for any reason, we'll get a rougue dangling pointer access on # our hands. The horror! if old_model is not None: import gc model_referrers = gc.get_referrers(old_model) if len(model_referrers) > 1: _logger.warning( "Extra references to the old model %r: %r", old_model, model_referrers, ) # Update the widget - all root items are expanded by default for row in itertools.count(): index = self._tree.model().index(row, 0) if not index.isValid(): break self._tree.expand(index) self._reset_selected_button.setEnabled(False) self._read_selected_button.setEnabled(False) self._read_all_button.setEnabled(len(filtered_registers) > 0) self._reset_all_button.setEnabled(len(filtered_registers) > 0) self._export_button.setEnabled(len(filtered_registers) > 0) self._import_button.setEnabled(len(filtered_registers) > 0) self._display_status(f"{len(filtered_registers)} registers loaded") def _on_visibility_changed(self): self._replace_model(self._visibility_selector.currentData()) def _on_selection_changed(self): selected = self._get_selected_registers() self._reset_selected_button.setEnabled( any(map(lambda r: r.has_default_value, selected))) self._read_selected_button.setEnabled(len(selected) > 0) def _do_read_selected(self): selected = self._get_selected_registers() if selected: self._read_specific(selected) else: self._display_status("No registers are selected, nothing to read") def _do_reset_selected(self): rv = {} for r in self._get_selected_registers(): if r.has_default_value: rv[r] = r.default_value self._write_specific(rv) def _do_reset_all(self): rv = {} for r in self._registers: if r.has_default_value: rv[r] = r.default_value self._write_specific(rv) def _do_read_all(self): self._read_specific(self._tree.model().registers) def _do_import(self): import_registers(parent=self, registers=self._registers) def _do_export(self): export_registers(parent=self, registers=self._registers) def _read_specific(self, registers: typing.List[Register]): total_registers_read = None def progress_callback(register: Register, current_register_index: int, total_registers: int): nonlocal total_registers_read total_registers_read = total_registers self._display_status( f"Reading {register.name!r} " f"({current_register_index + 1} of {total_registers})") async def executor(): try: _logger.info("Reading registers: %r", [r.name for r in registers]) mod: Model = self._tree.model() await mod.read(registers=registers, progress_callback=progress_callback) except asyncio.CancelledError: self._display_status(f"Read has been cancelled") raise except Exception as ex: _logger.exception("Register read failed") show_error("Read failed", "Could not read registers", repr(ex), self) self._display_status(f"Could not read registers: {ex!r}") else: self._display_status( f"{total_registers_read} registers have been read") self._cancel_task() self._running_task = asyncio.get_event_loop().create_task(executor()) def _write_specific(self, register_value_mapping: typing.Dict[Register, typing.Any]): total_registers_assigned = None def progress_callback(register: Register, current_register_index: int, total_registers: int): nonlocal total_registers_assigned total_registers_assigned = total_registers self._display_status( f"Writing {register.name!r} " f"({current_register_index + 1} of {total_registers})") async def executor(): try: _logger.info( "Writing registers: %r", [r.name for r in register_value_mapping.keys()], ) mod: Model = self._tree.model() await mod.write( register_value_mapping=register_value_mapping, progress_callback=progress_callback, ) except asyncio.CancelledError: self._display_status(f"Write has been cancelled") raise except Exception as ex: _logger.exception("Register write failed") show_error("Write failed", "Could not read registers", repr(ex), self) self._display_status(f"Could not write registers: {ex!r}") else: self._display_status( f"{total_registers_assigned} registers have been written") self._cancel_task() self._running_task = asyncio.get_event_loop().create_task(executor()) def _get_selected_registers(self) -> typing.List[Register]: selected_indexes: typing.List[ QModelIndex] = self._tree.selectedIndexes() selected_registers = set() for si in selected_indexes: r = Model.get_register_from_index(si) if r is not None: selected_registers.add(r) # Beware that sets are not sorted, this may lead to weird user experience when watching the registers # read in a funny order. return list(sorted(selected_registers, key=lambda x: x.name)) def _cancel_task(self): # noinspection PyBroadException try: self._running_task.cancel() except Exception: pass else: _logger.info("A running task had to be cancelled: %r", self._running_task) finally: self._running_task = None def _display_status(self, text=None): self._status_display.setText(text)
class FileTreeView(QWidget): on_menu_select = pyqtSignal(str, dict) def __init__(self): super().__init__() self.list_file = [] self.get_data_file() v_box = QVBoxLayout() v_box.addLayout(self.finder()) v_box.addWidget(self.tree_view()) self.setLayout(v_box) def finder(self): w_find = QLineEdit() w_find.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) w_find_button = QPushButton() w_find_button.setText("Find") w_find_button.setFixedWidth(50) h_box = QHBoxLayout() h_box.addWidget(w_find) h_box.addWidget(w_find_button, 1) return h_box def get_data_file(self): self.list_file = self.create_list(get_data_folder()) def create_list(self, dir): lst = os.listdir(path=dir) list_file = [] for f in lst: path = dir + "\\" + f file = {} if os.path.isdir(path): file["dir"] = dir file["name"] = f file["isDir"] = True file["child"] = self.create_list(path) else: file["dir"] = dir file["name"] = f file["isDir"] = False list_file.append(file) return list_file def tree_view(self): self.tree = QTreeView() self.tree.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.tree.setContextMenuPolicy(Qt.CustomContextMenu) self.tree.customContextMenuRequested.connect(self.openMenu) self.model = QtGui.QStandardItemModel() self.model.setHorizontalHeaderLabels(['List API']) self.tree.header().setDefaultSectionSize(180) self.tree.setModel(self.model) self.import_data(self.model.invisibleRootItem(), self.list_file) self.tree.expandAll() return self.tree def import_data(self, parent_item, list): for i in list: if i["isDir"]: item = QStandardItem(i["name"]) item.setEditable(False) item.setIcon(QIcon(get_icon_link("folder_yellow.svg"))) item.setData(i) parent_item.appendRow(item) self.import_data(item, i["child"]) else: item = QStandardItem(i["name"]) item.setEditable(False) item.setIcon(QIcon(get_icon_link("text_snippet.svg"))) item.setData(i) parent_item.appendRow(item) def openMenu(self, position): indexes = self.tree.selectedIndexes() level = 0 data = {} if len(indexes) > 0: index = indexes[0] item = self.model.itemFromIndex(index) data = item.data() if data['isDir']: level = 1 else: level = 2 menu = QMenu() menu.setStyleSheet(open('stylesheet/default.qss').read()) # Create preference action rename_action = QAction(QIcon(get_icon_link('edit.svg')), '&Rename', self) rename_action.setStatusTip('Rename') # exitAction.triggered.connect(self.exitCall) # Create preference action new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')), '&New Folder', self) new_action.setStatusTip('New Folder') # exitAction.triggered.connect(self.exitCall) # Create preference action refresh_action = QAction(QIcon(get_icon_link('refresh.svg')), '&Refresh', self) refresh_action.setStatusTip('Refresh') # exitAction.triggered.connect(self.exitCall) # Create preference action delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')), '&Delete', self) delete_action.setStatusTip('Delete') # exitAction.triggered.connect(self.exitCall) # Create preference action open_action = QAction(QIcon(get_icon_link('open_in_new.svg')), '&Open', self) open_action.setStatusTip('Open file') # open_action.triggered.connect(self.open_file) # Create preference action expand_action = QAction(QIcon(), '&Expand', self) expand_action.setStatusTip('Expand') # exitAction.triggered.connect(self.exitCall) # Create preference action collapse_action = QAction(QIcon(), '&Collapse', self) collapse_action.setStatusTip('Collapse') # exitAction.triggered.connect(self.exitCall) # Create preference action copy_action = QAction(QIcon(get_icon_link('content_copy.svg')), '&Copy', self) copy_action.setStatusTip('Copy') # exitAction.triggered.connect(self.exitCall) # Create preference action move_action = QAction(QIcon(get_icon_link('zoom_out_map.svg')), '&Move', self) move_action.setStatusTip('Move') # exitAction.triggered.connect(self.exitCall) if level == 1: menu.addAction(rename_action) menu.addAction(new_action) menu.addSeparator() menu.addAction(refresh_action) menu.addAction(expand_action) menu.addAction(collapse_action) menu.addSeparator() menu.addAction(delete_action) elif level == 2: menu.addAction(open_action) menu.addAction(refresh_action) menu.addSeparator() menu.addAction(rename_action) menu.addAction(copy_action) menu.addAction(move_action) menu.addSeparator() menu.addAction(delete_action) else: menu.addAction(new_action) menu.addAction(refresh_action) action = menu.exec_(self.tree.viewport().mapToGlobal(position)) if action == open_action: self.on_menu_select.emit("open", data)
class FileTreeView(QWidget): on_menu_select = pyqtSignal(str, str) on_dir_change = pyqtSignal() def __init__(self, parent: Application): super().__init__() self.stopped = threading.Event() self.tree = QTreeView() self.handle = self.Handler(self) self.get_data_file() self.model = QtGui.QStandardItemModel() self.item_construct = {} v_box = QVBoxLayout() v_box.addWidget(self.finder()) v_box.addWidget(self.tree_view()) self.setLayout(v_box) self.watch_dog() parent.app_close.connect(self.exit_push) self.tree.setAlternatingRowColors(True) def exit_push(self): self.stopped.set() def finder(self): w_find = QLineEdit() w_find.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) w_find.textChanged.connect(self.sort_list) w_find.setPlaceholderText("Search file..") return w_find def sort_list(self, text): list_sort = [] if text == "": self.show_tree(self.list_file) else: for data in self.list_file: if text.lower() in data.name().lower(): list_sort.append(data) self.show_tree(list_sort) def watch_dog(self): watch = ProcessRunnable(target=watch_winform, args=(get_data_folder(), self.handle, self.stopped)) watch.start() class Handler(watchdog.events.PatternMatchingEventHandler): def __init__(self, parent): super().__init__() self.parent = parent def on_created(self, event): print("Watchdog received created event", event.src_path, sep=" : ") asyncio.run( self.parent.file_change('create', event.src_path, event.is_directory)) def on_modified(self, event): print("Watchdog received modified event", event.src_path, sep=" : ") asyncio.run( self.parent.file_change('modify', event.src_path, event.is_directory)) def on_moved(self, event): print("Watchdog received move event", event.src_path, event.dest_path, sep=" : ") asyncio.run( self.parent.file_change('move', event.src_path, event.is_directory, event.dest_path)) def on_deleted(self, event): print("Watchdog received delete event", event.src_path, sep=" : ") asyncio.run( self.parent.file_change('delete', event.src_path, event.is_directory)) async def file_change(self, tpe, old, is_directory, new=""): if tpe == "move": self.import_single(self.model.invisibleRootItem(), new) self.remove_single(old) elif tpe == "delete": self.remove_single(old) elif tpe == "create": self.import_single(self.model.invisibleRootItem(), old) if is_directory: self.on_dir_change.emit() def get_data_file(self): self.list_file = [] self.create_list(get_data_folder()) def create_list(self, dir): lst = os.listdir(path=dir) for f in lst: path = os.path.join(dir, f) file = MyFile() if os.path.isdir(path): file.setParentName(os.path.basename(dir)) file.setParent(dir) file.setName(f) file.setDir(True) self.list_file.append(file) self.create_list(path) else: file.setParentName(os.path.basename(dir)) file.setParent(dir) file.setName(f) file.setDir(False) self.list_file.append(file) def tree_view(self): self.tree.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.tree.setContextMenuPolicy(Qt.CustomContextMenu) self.tree.customContextMenuRequested.connect(self.open_menu) self.tree.doubleClicked.connect(self.open_event) # self.model.itemChanged.connect(self.data_change) self.tree.setModel(self.model) self.show_tree(self.list_file) return self.tree def show_tree(self, list_file): self.model.clear() self.model.setHorizontalHeaderLabels(['List API']) self.tree.header().setDefaultSectionSize(180) parent = self.model.invisibleRootItem() self.item_construct = {} self.import_data(parent, list_file) self.tree.expandAll() def import_data_by_path(self, parent, path: list, index): if index < len(path): full = os.sep.join(path[:index + 1]) if full in self.item_construct: item = self.item_construct[full] else: item = QStandardItem(path[index]) if os.path.isfile(os.path.join(get_data_folder(), full)): item.setToolTip( self.read_description( os.path.join(get_data_folder(), full))) item.setEditable(False) if not os.path.isdir(os.path.join(get_data_folder(), full)): item.setIcon(QIcon(get_icon_link("text_snippet.svg"))) else: item.setIcon(QIcon(get_icon_link("folder_yellow.svg"))) item.setData(full) parent.appendRow(item) self.item_construct[full] = item self.import_data_by_path(item, path, index + 1) def read_description(self, path): try: data = json.loads(open(path, encoding='utf-8').read()) json_data = APIData() json_data.construct(data) x = json_data.parseSave().description() if x.isspace() or x == "": return ".." else: return x except Exception as ex: print(ex) return ".." def import_data(self, parent, list_data): for i in list_data: self.import_single(parent, i) def import_single(self, parent, file_path): path = self.path_extract(file_path) self.import_data_by_path(parent, path, 0) def remove_single(self, file_path): path = self.path_extract(file_path) full = os.sep.join(path[:len(path)]) if full in self.item_construct: item = self.item_construct[full] (item.parent() or self.model.invisibleRootItem()).removeRow(item.row()) del self.item_construct[full] def path_extract(self, file_path): if isinstance(file_path, MyFile): path = os.path.join(file_path.parent(), file_path.name()) else: path = file_path path = path.replace(get_data_folder(), "") if path.startswith(os.sep): path = path.replace(os.sep, "", 1) path = path.split(os.sep) return path def open_menu(self, position): indexes = self.tree.selectedIndexes() level = 0 data = "" item = None if len(indexes) > 0: index = indexes[0] item = self.model.itemFromIndex(index) data = item.data() data = os.path.join(get_data_folder(), data) if os.path.isdir(data): level = 1 else: level = 2 menu = QMenu() menu.setStyleSheet(open(get_stylesheet()).read()) rename_action = QAction(QIcon(get_icon_link('edit.svg')), '&Rename', self) rename_action.setStatusTip('Rename') new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')), '&New Folder', self) new_action.setStatusTip('New Folder') refresh_action = QAction(QIcon(get_icon_link('refresh.svg')), '&Refresh', self) refresh_action.setStatusTip('Refresh') delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')), '&Delete', self) delete_action.setStatusTip('Delete') open_action = QAction(QIcon(get_icon_link('open_in_new.svg')), '&Open', self) open_action.setStatusTip('Open file') expand_action = QAction(QIcon(), '&Expand', self) expand_action.setStatusTip('Expand') collapse_action = QAction(QIcon(), '&Collapse', self) collapse_action.setStatusTip('Collapse') duplicate_action = QAction(QIcon(get_icon_link('content_copy.svg')), '&Duplicate', self) duplicate_action.setStatusTip('Duplicate') copy_action = QAction(QIcon(get_icon_link('content_copy.svg')), '&Copy', self) copy_action.setStatusTip('Copy') move_action = QAction(QIcon(get_icon_link('zoom_out_map.svg')), '&Move', self) move_action.setStatusTip('Move') if level == 1: menu.addAction(rename_action) menu.addAction(new_action) menu.addSeparator() menu.addAction(refresh_action) menu.addAction(expand_action) menu.addAction(collapse_action) menu.addSeparator() menu.addAction(delete_action) elif level == 2: menu.addAction(open_action) menu.addAction(new_action) menu.addAction(refresh_action) menu.addSeparator() menu.addAction(rename_action) menu.addAction(duplicate_action) menu.addAction(copy_action) menu.addAction(move_action) menu.addSeparator() menu.addAction(delete_action) else: menu.addAction(new_action) menu.addAction(refresh_action) action = menu.exec_(self.tree.viewport().mapToGlobal(position)) if action == open_action: if data != "": self.on_menu_select.emit("open", data) elif action == refresh_action: self.get_data_file() self.show_tree(self.list_file) elif action == expand_action: if item is not None: self.tree.expand(item.index()) elif action == collapse_action: if item is not None: self.tree.collapse(item.index()) elif action == delete_action: if data != "": msg = QMessageBox() msg.setStyleSheet(open(get_stylesheet()).read()) msg.setIcon(QMessageBox.Warning) msg.setBaseSize(QSize(500, 300)) msg.setText("Delete file.") msg.setInformativeText("Are you sure to detele " + os.path.basename(data) + "?") msg.setWindowTitle("Delete Warning!!!") msg.addButton('Delete', QMessageBox.YesRole) msg.addButton('Move to Trash', QMessageBox.YesRole) msg.addButton('Cancel', QMessageBox.NoRole) rs = msg.exec_() if rs == 0: if os.path.isdir(data): shutil.rmtree(data) else: os.remove(data) elif rs == 1: send2trash(data) elif action == new_action: if data == "": data = get_data_folder() # input_name = QInputDialog() # input_name.setStyleSheet(open(get_stylesheet()).read()) # text, ok = input_name.getText(self, 'New Folder', 'Folder name:') inp = QComboDialog('New Folder', 'Folder name:', QComboDialog.Text) ok = inp.exec_() if ok and inp.select: if os.path.isdir(data): try: os.mkdir(os.path.join(data, inp.select)) except Exception as ex: alert = Alert("Error", "Create folder error", str(ex)) alert.exec_() print(ex) else: new = os.path.join(os.path.dirname(data), inp.select) try: os.mkdir(new) except Exception as ex: alert = Alert("Error", "Create folder error", str(ex)) alert.exec_() print(ex) elif action == rename_action: if data != "": # input_name = QInputDialog() # input_name.setStyleSheet(open(get_stylesheet()).read()) # text, ok = input_name.getText(self, 'Rename file', 'New name:') inp = QComboDialog('Rename file', 'New name:', QComboDialog.Text) ok = inp.exec_() if ok and inp.select: if os.path.isdir(data): new = os.path.join(os.path.dirname(data), inp.select) try: os.rename(data, new) except Exception as ex: alert = Alert("Error", "Rename folder error", str(ex)) alert.exec_() print(ex) else: filename, file_extension = os.path.splitext(data) new = os.path.join(os.path.dirname(data), inp.select + file_extension) try: os.rename(data, new) except Exception as ex: alert = Alert("Error", "Rename file error", str(ex)) alert.exec_() print(ex) elif action == move_action: if data != "": items = get_list_folder(get_data_folder(), get_data_folder()) # # item, ok = QInputDialog.getItem(self, "Select folder dialog", # "Select the destination folder", items, 0, False) inp = QComboDialog("Move", "Select the destination folder", QComboDialog.ComboBox, items) ok = inp.exec_() if ok and inp.select: folder = inp.select if inp.select.startswith(os.sep): folder = inp.select.replace(os.sep, "", 1) new = os.path.join(get_data_folder(), folder, os.path.basename(data)) try: os.rename(data, new) except Exception as ex: alert = Alert("Error", "Move file error", str(ex)) alert.exec_() print(ex) elif action == duplicate_action: if data != "": # input_name = QInputDialog() # input_name.setStyleSheet(open(get_stylesheet()).read()) # text, ok = input_name.getText(self, 'Duplicate file', 'New name:') inp = QComboDialog('Duplicate file', 'New name:', QComboDialog.Text) filename, file_extension = os.path.splitext(data) inp.set_init_text(filename) ok = inp.exec_() if ok and inp.select: new = os.path.join(os.path.dirname(data), inp.select + file_extension) try: copyfile(data, new) except Exception as ex: alert = Alert("Error", "Duplicate file error", str(ex)) alert.exec_() print(ex) elif action == copy_action: if data != "": items = get_list_folder(get_data_folder(), get_data_folder()) inp = QComboDialog("Copy", "Select the destination folder", QComboDialog.ComboBox, items) ok = inp.exec_() # item, ok = QInputDialog.getItem(self, "Select folder dialog", # "Select the destination folder", items, 0, False) if ok and inp.select: folder = inp.select if inp.select.startswith(os.sep): folder = inp.select.replace(os.sep, "", 1) new = os.path.join(get_data_folder(), folder, os.path.basename(data)) try: copyfile(data, new) except Exception as ex: alert = Alert("Error", "Copy file error", str(ex)) alert.exec_() print(ex) def open_event(self, index): item = self.model.itemFromIndex(index) if item is not None: data = item.data() data = os.path.join(get_data_folder(), data) if os.path.isdir(data): level = 1 else: level = 2 if data != "": if level == 2: self.on_menu_select.emit("open", data) elif level == 1: if not self.tree.isExpanded(item.index()): self.tree.collapse(item.index()) else: self.tree.expand(item.index())
class DirectoriesDialog(QMainWindow): def __init__(self, app, **kwargs): super().__init__(None, **kwargs) self.app = app self.specific_actions = set() self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS self.recentFolders = Recent(self.app, "recentFolders") self._setupUi() self._updateScanTypeList() self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView) self.directoriesDelegate = DirectoriesDelegate() self.treeView.setItemDelegate(self.directoriesDelegate) self._setupColumns() self.app.recentResults.addMenu(self.menuLoadRecent) self.app.recentResults.addMenu(self.menuRecentResults) self.recentFolders.addMenu(self.menuRecentFolders) self._updateAddButton() self._updateRemoveButton() self._updateLoadResultsButton() self._updateActionsState() self._setupBindings() def _setupBindings(self): self.appModeRadioBox.itemSelected.connect(self.appModeButtonSelected) self.showPreferencesButton.clicked.connect( self.app.actionPreferences.trigger) self.scanButton.clicked.connect(self.scanButtonClicked) self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger) self.addFolderButton.clicked.connect(self.actionAddFolder.trigger) self.removeFolderButton.clicked.connect(self.removeFolderButtonClicked) self.treeView.selectionModel().selectionChanged.connect( self.selectionChanged) self.app.recentResults.itemsChanged.connect( self._updateLoadResultsButton) self.recentFolders.itemsChanged.connect(self._updateAddButton) self.recentFolders.mustOpenItem.connect(self.app.model.add_directory) self.directoriesModel.foldersAdded.connect( self.directoriesModelAddedFolders) self.app.willSavePrefs.connect(self.appWillSavePrefs) def _setupActions(self): # (name, shortcut, icon, desc, func) ACTIONS = [ ( "actionLoadResults", "Ctrl+L", "", tr("Load Results..."), self.loadResultsTriggered, ), ( "actionShowResultsWindow", "", "", tr("Scan Results"), self.app.showResultsWindow, ), ("actionAddFolder", "", "", tr("Add Folder..."), self.addFolderTriggered), ("actionLoadDirectories", "", "", tr("Load Directories..."), self.loadDirectoriesTriggered), ("actionSaveDirectories", "", "", tr("Save Directories..."), self.saveDirectoriesTriggered), ] createActions(ACTIONS, self) if self.app.use_tabs: # Keep track of actions which should only be accessible from this window self.specific_actions.add(self.actionLoadDirectories) self.specific_actions.add(self.actionSaveDirectories) def _setupMenu(self): if not self.app.use_tabs: # we are our own QMainWindow, we need our own menu bar self.menubar = QMenuBar(self) self.menubar.setGeometry(QRect(0, 0, 42, 22)) self.menuFile = QMenu(self.menubar) self.menuFile.setTitle(tr("File")) self.menuView = QMenu(self.menubar) self.menuView.setTitle(tr("View")) self.menuHelp = QMenu(self.menubar) self.menuHelp.setTitle(tr("Help")) self.setMenuBar(self.menubar) menubar = self.menubar else: # we are part of a tab widget, we populate its window's menubar instead self.menuFile = self.app.main_window.menuFile self.menuView = self.app.main_window.menuView self.menuHelp = self.app.main_window.menuHelp menubar = self.app.main_window.menubar self.menuLoadRecent = QMenu(self.menuFile) self.menuLoadRecent.setTitle(tr("Load Recent Results")) self.menuFile.addAction(self.actionLoadResults) self.menuFile.addAction(self.menuLoadRecent.menuAction()) self.menuFile.addSeparator() self.menuFile.addAction(self.app.actionClearPictureCache) self.menuFile.addSeparator() self.menuFile.addAction(self.actionLoadDirectories) self.menuFile.addAction(self.actionSaveDirectories) self.menuFile.addSeparator() self.menuFile.addAction(self.app.actionQuit) self.menuView.addAction(self.app.actionDirectoriesWindow) self.menuView.addAction(self.actionShowResultsWindow) self.menuView.addAction(self.app.actionIgnoreList) self.menuView.addSeparator() self.menuView.addAction(self.app.actionPreferences) self.menuHelp.addAction(self.app.actionShowHelp) self.menuHelp.addAction(self.app.actionOpenDebugLog) self.menuHelp.addAction(self.app.actionAbout) menubar.addAction(self.menuFile.menuAction()) menubar.addAction(self.menuView.menuAction()) menubar.addAction(self.menuHelp.menuAction()) # Recent folders menu self.menuRecentFolders = QMenu() self.menuRecentFolders.addAction(self.actionAddFolder) self.menuRecentFolders.addSeparator() # Recent results menu self.menuRecentResults = QMenu() self.menuRecentResults.addAction(self.actionLoadResults) self.menuRecentResults.addSeparator() def _setupUi(self): self.setWindowTitle(self.app.NAME) self.resize(420, 338) self.centralwidget = QWidget(self) self.verticalLayout = QVBoxLayout(self.centralwidget) self.verticalLayout.setContentsMargins(4, 0, 4, 0) self.verticalLayout.setSpacing(0) hl = QHBoxLayout() label = QLabel(tr("Application Mode:"), self) label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(label) self.appModeRadioBox = RadioBox( self, items=[tr("Standard"), tr("Music"), tr("Picture")], spread=False) hl.addWidget(self.appModeRadioBox) self.verticalLayout.addLayout(hl) hl = QHBoxLayout() hl.setAlignment(Qt.AlignLeft) label = QLabel(tr("Scan Type:"), self) label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(label) self.scanTypeComboBox = QComboBox(self) self.scanTypeComboBox.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.scanTypeComboBox.setMaximumWidth(400) hl.addWidget(self.scanTypeComboBox) self.showPreferencesButton = QPushButton(tr("More Options"), self.centralwidget) self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(self.showPreferencesButton) self.verticalLayout.addLayout(hl) self.promptLabel = QLabel( tr('Select folders to scan and press "Scan".'), self.centralwidget) self.verticalLayout.addWidget(self.promptLabel) self.treeView = QTreeView(self.centralwidget) self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection) self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows) self.treeView.setAcceptDrops(True) triggers = (QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed | QAbstractItemView.SelectedClicked) self.treeView.setEditTriggers(triggers) self.treeView.setDragDropOverwriteMode(True) self.treeView.setDragDropMode(QAbstractItemView.DropOnly) self.treeView.setUniformRowHeights(True) self.verticalLayout.addWidget(self.treeView) self.horizontalLayout = QHBoxLayout() self.removeFolderButton = QPushButton(self.centralwidget) self.removeFolderButton.setIcon(QIcon(QPixmap(":/minus"))) self.removeFolderButton.setShortcut("Del") self.horizontalLayout.addWidget(self.removeFolderButton) self.addFolderButton = QPushButton(self.centralwidget) self.addFolderButton.setIcon(QIcon(QPixmap(":/plus"))) self.horizontalLayout.addWidget(self.addFolderButton) spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.loadResultsButton = QPushButton(self.centralwidget) self.loadResultsButton.setText(tr("Load Results")) self.horizontalLayout.addWidget(self.loadResultsButton) self.scanButton = QPushButton(self.centralwidget) self.scanButton.setText(tr("Scan")) self.scanButton.setDefault(True) self.horizontalLayout.addWidget(self.scanButton) self.verticalLayout.addLayout(self.horizontalLayout) self.setCentralWidget(self.centralwidget) self._setupActions() self._setupMenu() if self.app.prefs.directoriesWindowRect is not None: self.setGeometry(self.app.prefs.directoriesWindowRect) else: moveToScreenCenter(self) def _setupColumns(self): header = self.treeView.header() header.setStretchLastSection(False) header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.Fixed) header.resizeSection(1, 100) def _updateActionsState(self): self.actionShowResultsWindow.setEnabled( self.app.resultWindow is not None) def _updateAddButton(self): if self.recentFolders.isEmpty(): self.addFolderButton.setMenu(None) else: self.addFolderButton.setMenu(self.menuRecentFolders) def _updateRemoveButton(self): indexes = self.treeView.selectedIndexes() if not indexes: self.removeFolderButton.setEnabled(False) return self.removeFolderButton.setEnabled(True) def _updateLoadResultsButton(self): if self.app.recentResults.isEmpty(): self.loadResultsButton.setMenu(None) else: self.loadResultsButton.setMenu(self.menuRecentResults) def _updateScanTypeList(self): try: self.scanTypeComboBox.currentIndexChanged[int].disconnect( self.scanTypeChanged) except TypeError: # Not connected, ignore pass self.scanTypeComboBox.clear() scan_options = self.app.model.SCANNER_CLASS.get_scan_options() for scan_option in scan_options: self.scanTypeComboBox.addItem(scan_option.label) SCAN_TYPE_ORDER = [so.scan_type for so in scan_options] selected_scan_type = self.app.prefs.get_scan_type( self.app.model.app_mode) scan_type_index = SCAN_TYPE_ORDER.index(selected_scan_type) self.scanTypeComboBox.setCurrentIndex(scan_type_index) self.scanTypeComboBox.currentIndexChanged[int].connect( self.scanTypeChanged) self.app._update_options() # --- QWidget overrides def closeEvent(self, event): event.accept() if self.app.model.results.is_modified: title = tr("Unsaved results") msg = tr("You have unsaved results, do you really want to quit?") if not self.app.confirm(title, msg): event.ignore() if event.isAccepted(): self.app.shutdown() # --- Events def addFolderTriggered(self): title = tr("Select a folder to add to the scanning list") flags = QFileDialog.ShowDirsOnly dirpath = str( QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags)) if not dirpath: return self.lastAddedFolder = dirpath self.app.model.add_directory(dirpath) self.recentFolders.insertItem(dirpath) def appModeButtonSelected(self, index): if index == 2: mode = AppMode.Picture elif index == 1: mode = AppMode.Music else: mode = AppMode.Standard self.app.model.app_mode = mode self._updateScanTypeList() def appWillSavePrefs(self): self.app.prefs.directoriesWindowRect = self.geometry() def directoriesModelAddedFolders(self, folders): for folder in folders: self.recentFolders.insertItem(folder) def loadResultsTriggered(self): title = tr("Select a results file to load") files = ";;".join( [tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")]) destination = QFileDialog.getOpenFileName(self, title, "", files)[0] if destination: self.app.model.load_from(destination) self.app.recentResults.insertItem(destination) def loadDirectoriesTriggered(self): title = tr("Select a directories file to load") files = ";;".join( [tr("dupeGuru Results (*.dupegurudirs)"), tr("All Files (*.*)")]) destination = QFileDialog.getOpenFileName(self, title, "", files)[0] if destination: self.app.model.load_directories(destination) def removeFolderButtonClicked(self): self.directoriesModel.model.remove_selected() def saveDirectoriesTriggered(self): title = tr("Select a file to save your directories to") files = tr("dupeGuru Directories (*.dupegurudirs)") destination, chosen_filter = QFileDialog.getSaveFileName( self, title, "", files) if destination: if not destination.endswith(".dupegurudirs"): destination = "{}.dupegurudirs".format(destination) self.app.model.save_directories_as(destination) def scanButtonClicked(self): if self.app.model.results.is_modified: title = tr("Start a new scan") msg = tr( "You have unsaved results, do you really want to continue?") if not self.app.confirm(title, msg): return self.app.model.start_scanning() def scanTypeChanged(self, index): scan_options = self.app.model.SCANNER_CLASS.get_scan_options() self.app.prefs.set_scan_type(self.app.model.app_mode, scan_options[index].scan_type) self.app._update_options() def selectionChanged(self, selected, deselected): self._updateRemoveButton()
class EditScreen(QWidget): def __init__(self, rester: Rester): super(EditScreen, self).__init__() self.last_item_type = '' self.rester = rester self.tree = QTreeView() layout = QVBoxLayout() layout.addWidget(self.tree) self.model = QStandardItemModel() self.model.setHorizontalHeaderLabels(['Name']) self.tree.header().setDefaultSectionSize(200) self.tree.setModel(self.model) self.data = self.rester.list_request(list(APIEnum)) self.import_data(self.data) self.tree.setSortingEnabled(False) self.tree.expandAll() self.tree.setSelectionMode(QAbstractItemView.SingleSelection) self.tree.selectionModel().selectionChanged.connect(self.item_selected) self.init_ui() def init_ui(self): hbox = QHBoxLayout() vbox = QVBoxLayout() left_layout = QFormLayout() tree_group_box = QGroupBox() self.search_field = QLineEdit() # self.search_field.editingFinished.connect(self.search_field_changed) self.search_field.textEdited.connect(self.search_field_changed) left_layout.addRow(QLabel('Search:'), self.search_field) tree_group_box.setLayout(left_layout) vbox.addWidget(tree_group_box) vbox.addWidget(self.tree) gb = QGroupBox() gb.setLayout(vbox) self.splitter = QSplitter() self.splitter.addWidget(gb) self.ew = EquipmentEdit() self.splitter.addWidget(self.ew) hbox.addWidget(self.splitter) self.setLayout(hbox) def import_data(self, data, root=None): self.model.setRowCount(0) if root is None: root = self.model.invisibleRootItem() for key, value_list in data.items(): parent = TreeItem({'name': key}, '', parent=True) parent.setEditable(False) root.appendRow([parent]) for value in value_list: parent.appendRow([TreeItem(value, key)]) def search_field_changed(self): term = self.search_field.text() data = {} for key, value_list in self.data.items(): li = [] for value in value_list: for k in value.keys(): if term in k: li.append(value) data[key] = li self.import_data(data) def item_selected(self): indexes = self.tree.selectedIndexes() selected = indexes[0] item = self.model.itemFromIndex(selected) if item.is_parent: return #if item.typ != self.last_item_type: self.ew.hide() self.ew.destroy() self.ew = get_edit_widget(item.typ, self.data) self.splitter.addWidget(self.ew) self.last_item_type = item.typ data_dict = item.get_data_dict() self.ew.load_data(data_dict)
class ZeroConfExplorer(QWidget): """ create a zeroconf qgroubbox with a qlist view """ def __init__(self, name): super(ZeroConfExplorer, self).__init__() # init explorer to None self.oscquery_device = None if not name: name = 'OSCJSON thru TCP Explorer' # create the view self.explorer = QTreeView() # Hide Useless Header self.explorer.header().hide() self.panel = Panel() # create right-click menu self.explorer.setContextMenuPolicy(Qt.CustomContextMenu) self.explorer.customContextMenuRequested.connect(self.contextual_menu) # create the model self.devices_model = QStandardItemModel() # link model to the view self.explorer.setModel(self.devices_model) self.explorer.selectionModel().selectionChanged.connect( self.selection_updated) # set selection self.device_selection_model = self.explorer.selectionModel() # set layout and group Layout = QGridLayout() # add the view to the layout Layout.addWidget(self.explorer, 0, 0) Layout.addWidget(self.panel, 0, 1) # add the layout to the GroupBox self.setLayout(Layout) #self.setMinimumSize(300, 300) self.explorer.setFixedSize(300, 300) # start zeroconf services zeroconf = Zeroconf() # start the callback, it will create items listener = ZeroConfListener(self.devices_model) listener.add_device.connect(self.connect_device) browser = ServiceBrowser(zeroconf, "_oscjson._tcp.local.", listener) self.current_remote = None def connect_device(self, device): self.panel.device = device def contextual_menu(self, position): indexes = self.explorer.selectedIndexes() if len(indexes) > 0: level = 0 index = indexes[0] while index.parent().isValid(): index = index.parent() level += 1 node = self.devices_model.itemFromIndex(index) menu = QMenu() if level == 0: menu.addAction("Refresh Device Namespace", node.update) elif level > 0: menu.addAction("Refresh Node", node.update) menu.exec_(self.explorer.viewport().mapToGlobal(position)) def selection_updated(self, *args, **kwargs): """ called when device selection is updated we will disconnect our ossia.OSCQueryDevice from the previous device if there was one and then we will reconnect it to the current instance of the Device model """ index = self.device_selection_model.selectedIndexes() # we consider unique selection modelIndex = index[0] if modelIndex: if self.current_remote: self.panel.layout.removeWidget(self.current_remote) self.current_remote.deleteLater() del self.current_remote self.current_remote = None node = self.devices_model.itemFromIndex(modelIndex).node if node.__class__.__name__ == 'OSCQueryDevice': print('Device') else: if node.parameter: self.current_remote = self.panel.add_remote(node.parameter) else: self.current_remote = self.panel.add_inspector(node) else: print('no node selected')
class AddToProject(QDialog): """Dialog to let the user choose one of the folders from the opened proj""" def __init__(self, projects, parent=None): super(AddToProject, self).__init__(parent) #pathProjects must be a list self._projects = projects self.setWindowTitle(translations.TR_ADD_FILE_TO_PROJECT) self.pathSelected = '' vbox = QVBoxLayout(self) hbox = QHBoxLayout() self._list = QListWidget() for project in self._projects: self._list.addItem(project.name) self._list.setCurrentRow(0) self._tree = QTreeView() #self._tree.header().setHidden(True) self._tree.setSelectionMode(QTreeView.SingleSelection) self._tree.setAnimated(True) self.load_tree(self._projects[0]) hbox.addWidget(self._list) hbox.addWidget(self._tree) vbox.addLayout(hbox) hbox2 = QHBoxLayout() btnAdd = QPushButton(translations.TR_ADD_HERE) btnCancel = QPushButton(translations.TR_CANCEL) hbox2.addWidget(btnCancel) hbox2.addWidget(btnAdd) vbox.addLayout(hbox2) btnCancel.connect(self.close) btnAdd.connect(self._select_path) self._list.currentItemChanged['QTreeWidgetItem*', 'QTreeWidgetItem*'].connect(self._project_changed) def _project_changed(self, item, previous): #FIXME, this is not being called, at least in osx for each_project in self._projects: if each_project.name == item.text(): self.load_tree(each_project) def load_tree(self, project): """Load the tree view on the right based on the project selected.""" qfsm = QFileSystemModel() qfsm.setRootPath(project.path) load_index = qfsm.index(qfsm.rootPath()) qfsm.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot) qfsm.setNameFilterDisables(False) pext = ["*{0}".format(x) for x in project.extensions] qfsm.setNameFilters(pext) self._tree.setModel(qfsm) self._tree.setRootIndex(load_index) t_header = self._tree.header() t_header.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) t_header.setSectionResizeMode(0, QHeaderView.Stretch) t_header.setStretchLastSection(False) t_header.setClickable(True) self._tree.hideColumn(1) # Size self._tree.hideColumn(2) # Type self._tree.hideColumn(3) # Modification date #FIXME: Changing the name column's title requires some magic #Please look at the project tree def _select_path(self): """Set pathSelected to the folder selected in the tree.""" path = self._tree.model().filePath(self._tree.currentIndex()) if path: self.pathSelected = path self.close()
from model import FileSystemTreeModel, ActionDelegate if __name__ == "__main__": app = QApplication(sys.argv) model = FileSystemTreeModel(Node('Filename'), path='/') tree = QTreeView() tree.setModel(model) tree.setIconSize(QSize(48, 48)) tree.setHeaderHidden(False) # tree.setColumnWidth(0, 250) tree.setColumnWidth(1, 30) hv = tree.header() hv.setStretchLastSection(False) hv.setSectionResizeMode(0, QHeaderView.Stretch) hv.setSectionResizeMode(1, QHeaderView.Fixed) tree.setWordWrap(True) tree.setTextElideMode(Qt.ElideNone) act_delegate = ActionDelegate() tree.setItemDelegateForColumn(1, act_delegate) # tree.setFixedSize(700, 900) tree.setMinimumSize(700, 900) @pyqtSlot('QPoint') def open_menu(pos): """ :type pos: QPoint
class DriveAnalysisWidget(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Drive Analysis Tool') # self.root_path = os.path.expanduser('~') # self.root_path = os.path.expanduser('~\\Downloads') self.root_path = os.path.expanduser( os.path.join('~', 'Dropbox', 'mcgill')) self.root_path2 = '' self.dbx_json_dirpath = '/' self.have_two_roots = False self.threadpool = QThreadPool() self.expanded_items_list = [] self.unchecked_items_list = [] self.unchecked_items_set = set() self.renamed_items_dict = dict() # with open(os.path.expanduser(os.path.join('~', 'Dropbox', 'mcgill', 'File Zoomer', # 'code', 'drive_analysis_tool', 'dir_dict.pkl')), 'rb') as ddf: # self.og_dir_dict = pickle.load(ddf) # self.anon_dir_dict = deepcopy(self.og_dir_dict) self.og_dir_dict, self.anon_dir_dict = dict(), dict() self.og_dir_dict2, self.anon_dir_dict2 = dict(), dict() self.user_folder_props = dict() self.user_folder_typical = True self.build_tree_structure_threaded(self.root_path) # test_btn = QPushButton() # test_btn.setText('Run tests') # test_btn.resize(test_btn.sizeHint()) # test_btn.clicked.connect(self.test_script) select_btn = QPushButton('Select Root 1', self) select_btn.setToolTip( 'Select <b>personal folder 1</b> for data collection.') select_btn.clicked.connect(self.show_file_dialog) select_btn.resize(select_btn.sizeHint()) select_btn2 = QPushButton('Select Root 2', self) select_btn2.setToolTip( 'Select <b>personal folder 2</b> (if present) for data collection.' ) select_btn2.clicked.connect(self.show_file_dialog2) select_btn2.resize(select_btn2.sizeHint()) preview_btn = QPushButton('Preview', self) preview_btn.setToolTip( 'Preview folder data that will be used for research') preview_btn.clicked.connect(self.preview_anon_tree_threaded) preview_btn.resize(preview_btn.sizeHint()) self.submit_btn = QPushButton('Submit', self) self.submit_btn.setToolTip('Submit encrypted folder data to the cloud') self.submit_btn.clicked.connect(self.upload_collected_data) self.submit_btn.resize(self.submit_btn.sizeHint()) self.submit_btn.setEnabled(False) self.folder_edit = QLabel() self.folder_edit.setText(self.root_path) self.folder_edit2 = QLabel() self.folder_edit2.setText(self.root_path2) self.status_label = QLabel() self.status_label.setText('') self.status_label.setStyleSheet("color: red;" "font: bold;") self.status_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.user_folder_props_label = QLabel() self.user_folder_props_label.setAlignment(Qt.AlignCenter) self.user_folder_props_label.setText( 'Characteristics of folder structure') self.user_folder_props_table = QTableWidget() self.user_folder_props_table.setRowCount(22) self.user_folder_props_table.setColumnCount(2) labels = [ 'Total files', 'Total folders', 'Greatest breadth of folder tree', 'Average breadth of folder tree', '# folders at root', '# leaf folders (folders without subfolders)', '% leaf folders (folders without subfolders)', 'Average depth of leaf folders (folders without subfolders)', '# switch folders (folders with subfolders and no files)', '% switch folders (folders with subfolders and no files)', 'Average depth of switch folders (folders with subfolders and no files)', 'Greatest depth where folders are found', 'Folder waist (depth where folders are most commonly found)', 'Average depth where folders are found', 'Branching factor (average subfolders per folder, excepting leaf folders)', '# files at root', 'Average # files in folders', '# empty folders', '% empty folders', 'Average depth where files are found', 'Depth where files are most commonly found', '# files at depth where files are most commonly found' ] label_keys = [ 'n_files', 'n_folders', 'breadth_max', 'breadth_mean', 'root_n_folders', 'n_leaf_folders', 'pct_leaf_folders', 'depth_leaf_folders_mean', 'n_switch_folders', 'pct_switch_folders', 'depth_switch_folders_mean', 'depth_max', 'depth_folders_mode', 'depth_folders_mean', 'branching_factor', 'root_n_files', 'n_files_mean', 'n_empty_folders', 'pct_empty_folders', 'depth_files_mean', 'depth_files_mode', 'file_breadth_mode_n_files' ] for row, label, label_key in zip(range(22), labels, label_keys): label_item = QTableWidgetItem(label) label_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) value_item = QTableWidgetItem('?') value_item.setData(Qt.UserRole, label_key) value_item.setTextAlignment(Qt.AlignRight) value_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.user_folder_props_table.setItem(row, 0, label_item) self.user_folder_props_table.setItem(row, 1, value_item) self.user_folder_props_table.setHorizontalHeaderLabels( ['Property', 'Value']) self.user_folder_props_table.horizontalHeader().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.user_folder_props_table.horizontalHeader().setSectionResizeMode( 1, QHeaderView.ResizeToContents) # print(self.user_folder_props_table.item(0, 1).data(Qt.UserRole)) # self.user_folder_typical_label = QLabel() # self.user_folder_typical_label.setText('') # self.user_folder_typical_label.setStyleSheet("font: bold;") # self.user_folder_typical_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) og_tree_label = QLabel() og_tree_label.setAlignment(Qt.AlignCenter) og_tree_label.setText('Original folders data') anon_tree_label = QLabel() anon_tree_label.setAlignment(Qt.AlignCenter) anon_tree_label.setText('Folders data to be used for research') self.og_tree = QTreeView() self.og_model = QStandardItemModel() self.og_tree.setModel(self.og_model) self.og_model.setHorizontalHeaderLabels( ['Folder Name', 'Renamed Folder', 'Number of Files']) self.og_root_item = self.og_model.invisibleRootItem() self.refresh_treeview(self.og_model, self.og_tree, self.og_dir_dict) self.og_tree.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.og_tree.header().setSectionResizeMode( 1, QHeaderView.ResizeToContents) self.og_tree.header().setSectionResizeMode( 2, QHeaderView.ResizeToContents) self.og_model.itemChanged.connect(self.on_item_change) self.anon_tree = QTreeView() self.anon_model = QStandardItemModel() self.anon_tree.setModel(self.anon_model) self.anon_model.setHorizontalHeaderLabels( ['Folder Name', 'Number of Files']) self.anon_root_item = self.anon_model.invisibleRootItem() self.refresh_treeview(self.anon_model, self.anon_tree, self.anon_dir_dict, checkable=False, anon_tree=True) self.anon_tree.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.anon_tree.header().setSectionResizeMode( 1, QHeaderView.ResizeToContents) grid = QGridLayout() grid.addWidget(select_btn, 0, 0, 1, 1) grid.addWidget(self.folder_edit, 0, 1, 1, 7) grid.addWidget(select_btn2, 1, 0, 1, 1) grid.addWidget(self.folder_edit2, 1, 1, 1, 7) # grid.addWidget(self.status_label, 1, 0, 1, 8) grid.addWidget(og_tree_label, 2, 0, 1, 5) grid.addWidget(anon_tree_label, 2, 5, 1, 3) grid.addWidget(self.og_tree, 3, 0, 1, 5) grid.addWidget(self.anon_tree, 3, 5, 1, 3) grid.addWidget(self.user_folder_props_label, 4, 0, 1, 8) grid.addWidget(self.user_folder_props_table, 5, 0, 2, 8) # grid.addWidget(self.user_folder_typical_label, 7, 0, 1, 6) grid.addWidget(self.status_label, 8, 0, 1, 6) grid.addWidget(preview_btn, 8, 6, 1, 1) grid.addWidget(self.submit_btn, 8, 7, 1, 1) self.setLayout(grid) self.resize(1280, 720) self.show() def refresh_treeview(self, model, tree, dir_dict, checkable=True, anon_tree=False, append=False): if not append: model.removeRow(0) root_item = model.invisibleRootItem() self.append_all_children( 1, dir_dict, root_item, checkable, anon_tree) # dir_dict key starts at 1 since 0==False # tree.setModel(model) tree.expandToDepth(0) def append_all_children(self, dirkey, dir_dict, parent_item, checkable=True, anon_tree=False): if dirkey in dir_dict: dirname = QStandardItem(dir_dict[dirkey]['dirname']) dirname_edited = QStandardItem(dir_dict[dirkey]['dirname']) nfiles = QStandardItem(str(dir_dict[dirkey]['nfiles'])) if anon_tree: items = [dirname, nfiles] else: items = [dirname, dirname_edited, nfiles] dirname.setData(dirkey, Qt.UserRole) dirname_edited.setData(dirkey, Qt.UserRole) if checkable: dirname.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserTristate | Qt.ItemIsUserCheckable) dirname.setCheckState(Qt.Checked) dirname_edited.setFlags(Qt.ItemIsEnabled | Qt.ItemIsEditable) nfiles.setFlags(Qt.ItemIsEnabled) parent_item.appendRow(items) child_ix = parent_item.rowCount() - 1 parent_item = parent_item.child(child_ix) children_keys = dir_dict[dirkey]['childkeys'] for child_key in sorted(children_keys): self.append_all_children(child_key, dir_dict, parent_item, checkable, anon_tree) def on_item_change(self, item): if item.column() == 0: dirkey = item.data(Qt.UserRole) if item.rowCount() == 0 and item.checkState( ) == Qt.PartiallyChecked: item.setCheckState(Qt.Checked) item_checkstate = item.checkState() parent_item = item.parent() if parent_item is None: nchild = item.rowCount() if nchild > 0: for child_ix in range(nchild): self.propagate_checkstate_child( item, child_ix, item_checkstate) if parent_item is not None: child_ix = item.row() self.propagate_checkstate_child(parent_item, child_ix, item_checkstate) self.propagate_checkstate_parent(item, item_checkstate) # self.unchecked_items_list = [] # self.list_unchecked(self.og_root_item, 0, self.unchecked_items_list) # print(self.unchecked_items_list) if item_checkstate == Qt.Unchecked: self.unchecked_items_set.add(dirkey) # if dirkey in self.renamed_items_dict: # self.renamed_items_dict.pop(dirkey) elif item_checkstate in (Qt.Checked, Qt.PartiallyChecked): if dirkey in self.unchecked_items_set: self.unchecked_items_set.remove(dirkey) self.status_label.setText('Click \'Preview\' to see changes') if item.column() == 1: dirkey = item.data(Qt.UserRole) self.renamed_items_dict[dirkey] = item.text() self.status_label.setText('Click \'Preview\' to see changes') def propagate_checkstate_child(self, parent_item, child_ix, parent_checkstate): if parent_checkstate != Qt.PartiallyChecked: parent_item.child(child_ix).setCheckState(parent_checkstate) parent_item = parent_item.child(child_ix) nchild = parent_item.rowCount() if nchild > 0: for child_ix in range(nchild): self.propagate_checkstate_child(parent_item, child_ix, parent_checkstate) def propagate_checkstate_parent(self, item, item_checkstate): parent_item = item.parent() if parent_item is not None: if self.all_sibling_checked(item): parent_item.setCheckState(Qt.Checked) if item_checkstate in ( Qt.Checked, Qt.PartiallyChecked ) and parent_item.checkState() == Qt.Unchecked: parent_item.setCheckState(Qt.PartiallyChecked) if item_checkstate in ( Qt.Unchecked, Qt.PartiallyChecked ) and parent_item.checkState() == Qt.Checked: parent_item.setCheckState(Qt.PartiallyChecked) def all_sibling_checked(self, item): all_checked = True if item.parent() is not None: parent_item = item.parent() nchild = parent_item.rowCount() for child_ix in range(nchild): if parent_item.child(child_ix).checkState() in ( Qt.Unchecked, Qt.PartiallyChecked): all_checked = False break return all_checked def expand_items(self, tree, parent_item, child_ix, expanded_items): item = parent_item.child(child_ix) if item.data(Qt.UserRole) in expanded_items: tree.setExpanded(item.index(), True) parent_item = parent_item.child(child_ix) nchild = parent_item.rowCount() if nchild > 0: for child_ix in range(nchild): self.expand_items(tree, parent_item, child_ix, expanded_items) def list_expanded(self, tree, parent_item, child_ix, expanded_items): # print(type(parent_item.child(0))) item = parent_item.child(child_ix) if tree.isExpanded(item.index()): expanded_items.append(item.data(Qt.UserRole)) parent_item = parent_item.child(child_ix) nchild = parent_item.rowCount() if nchild > 0: for child_ix in range(nchild): self.list_expanded(tree, parent_item, child_ix, expanded_items) def list_unchecked(self, parent_item, child_ix, unchecked_items): item = parent_item.child(child_ix) if item.checkState() == Qt.Unchecked: unchecked_items.append(item.data(Qt.UserRole)) parent_item = parent_item.child(child_ix) nchild = parent_item.rowCount() if nchild > 0: for child_ix in range(nchild): self.list_unchecked(parent_item, child_ix, unchecked_items) def on_item_change_threaded(self, item): worker = Worker(self.on_item_change, item) worker.signals.started.connect(self.on_item_change_started) worker.signals.result.connect(self.on_item_change_finished) self.threadpool.start(worker) def on_item_change_started(self): self.status_label.setText('Refreshing tree, please wait...') def on_item_change_finished(self): self.status_label.setText('') def build_tree_structure_threaded(self, root_path, root_ix=1, append_to_tree=False): worker = Worker(record_stat, root_path) worker.signals.started.connect(self.build_tree_started) worker.signals.result.connect(self.build_tree_midway) worker.signals.finished.connect( lambda: self.build_tree_finished(append_to_tree)) self.threadpool.start(worker) def build_tree_started(self): self.status_label.setText('Building tree, please wait...') def build_tree_midway(self, result): self.og_dir_dict = result # self.anon_dir_dict = deepcopy(self.og_dir_dict) self.anon_dir_dict = _pickle.loads(_pickle.dumps(self.og_dir_dict)) def build_tree_finished(self, append_to_tree): self.refresh_treeview(self.og_model, self.og_tree, self.og_dir_dict, append=append_to_tree) self.refresh_treeview(self.anon_model, self.anon_tree, self.anon_dir_dict, checkable=False, anon_tree=True, append=append_to_tree) self.status_label.setText('Click \'Preview\' to see changes') def preview_anon_tree(self): start = time.time() # self.anon_dir_dict = deepcopy(self.og_dir_dict) self.anon_dir_dict = _pickle.loads(_pickle.dumps(self.og_dir_dict)) print(start - time.time()) start = time.time() self.anon_dir_dict = anonymize_stat(self.anon_dir_dict, self.unchecked_items_set, self.renamed_items_dict) print(start - time.time()) start = time.time() self.refresh_treeview(self.anon_model, self.anon_tree, self.anon_dir_dict, checkable=False, anon_tree=True) print(start - time.time()) start = time.time() self.expanded_items_list = [] self.list_expanded(self.og_tree, self.og_root_item, 0, self.expanded_items_list) self.expand_items(self.anon_tree, self.anon_root_item, 0, self.expanded_items_list) print(start - time.time()) def preview_anon_tree_threaded(self): worker = Worker(self.preview_anon_tree) worker.signals.started.connect(self.preview_anon_tree_started) worker.signals.result.connect(self.preview_anon_tree_finished) self.threadpool.start(worker) def preview_anon_tree_started(self): self.status_label.setText('Constructing preview tree, please wait...') def preview_anon_tree_finished(self): # self.status_label.setText('') self.display_user_folder_props() def display_user_folder_props(self): self.user_folder_props = drive_measurement(self.anon_dir_dict) self.user_folder_typical = check_collection_properties( self.user_folder_props) for row in range(22): label_key = self.user_folder_props_table.item(row, 1).data(Qt.UserRole) value_item = QTableWidgetItem( str(round(self.user_folder_props[label_key], 1))) value_item.setData(Qt.UserRole, label_key) value_item.setTextAlignment(Qt.AlignRight) value_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.user_folder_props_table.setItem(row, 1, value_item) self.user_folder_props_table.reset() is_typical = self.user_folder_typical # is_typical = True if is_typical: self.submit_btn.setEnabled(True) is_typical_str = 'Values in nominal range, submit?' elif not is_typical: is_typical_str = 'Values are atypical, data not acceptable for submission' # self.user_folder_typical_label.setText(is_typical_str) self.status_label.setText(is_typical_str) def show_file_dialog(self): dirpath = QFileDialog.getExistingDirectory(self, 'Select Folder', self.root_path) if dirpath: self.root_path = os.path.abspath(dirpath) self.folder_edit.setText(self.root_path) self.build_tree_structure_threaded(self.root_path) def show_file_dialog2(self): dirpath = QFileDialog.getExistingDirectory(self, 'Select Folder', self.root_path) if dirpath: dirpath = os.path.abspath(dirpath) if Path(self.root_path) in Path(dirpath).parents: self.status_label.setText( 'Root folder 1 is a parent of root folder 2. ' 'Navigate the existing tree to find root folder 2.') elif Path(dirpath) in Path(self.root_path).parents: self.status_label.setText( 'Root folder 2 is a parent of root folder 1. ' 'Change root folder 1 to root folder 2 through ' '\'Select Root 1\'.') else: self.have_two_roots = True self.root_path2 = dirpath self.folder_edit2.setText(self.root_path2) self.build_tree_structure_threaded(self.root_path2, append_to_tree=True) def upload_collected_data(self): data = bytes(json.dumps(self.anon_dir_dict), 'utf8') data = compress_data(data) encrypted_json, encrypted_jsonkey = encrypt_data(data) dropbox_upload( encrypted_json, generate_filename(self.dbx_json_dirpath, suffix='_dir_dict.enc')) dropbox_upload( encrypted_jsonkey, generate_filename(self.dbx_json_dirpath, suffix='_sym_key.enc')) self.status_label.setText('Data uploaded. Thanks!') def test_script(self): unchecked_items_list = [] self.list_unchecked(self.root_item, 0, unchecked_items_list) print( set(self.og_dir_dict.keys()).difference(self.anon_dir_dict.keys())) print(unchecked_items_list)
class Installed(preferences.Group): """Overview of installed extensions. A QTreeView lists the metadata of all installed extensions. If the currently selected extension provides a configuration widget it is displayed in the bottom group of the page. With a checkbox individual extensions can be deactivated. Metadata is listed for all *installed* extensions, regardless of manual deactivation or load failure. """ def __init__(self, page): super(Installed, self).__init__(page) layout = QVBoxLayout() self.setLayout(layout) # This must be called before self.populate() because # the data model relies on the labels app.translateUI(self) self.tree = QTreeView() self.name_items = {} self._selected_extension = '' self.tree.setModel(QStandardItemModel()) self.tree.model().setColumnCount(2) self.tree.setSelectionBehavior(QAbstractItemView.SelectRows) self.tree.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tree.setHeaderHidden(True) self.tree.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.tree.selectionModel().selectionChanged.connect( self.selection_changed) self.populate() layout.addWidget(self.tree) def translateUI(self): self.setTitle(_("Installed Extensions")) self.config_labels = { 'extension-name': _("Name"), 'maintainers': _("Maintainer(s)"), 'version': _("Version"), 'api-version': _("API version"), 'license': _("License"), 'short-description': _("Short Description"), 'description': _("Description"), 'repository': _("Repository"), 'website': _("Website"), 'dependencies': _("Dependencies") } def loadSettings(self): s = QSettings() self.setEnabled(self.page().active()) s.beginGroup("extension-settings/installed") inactive = s.value("inactive", [], list) for ext in self.name_items.keys(): self.name_items[ext].setCheckState( Qt.Checked if ext not in inactive else Qt.Unchecked) self.tree.model().dataChanged.connect(self.page().changed) def saveSettings(self): s = QSettings() s.beginGroup("extension-settings/installed") inactive = [ext for ext in self.name_items.keys() if self.name_items[ext].checkState() == Qt.Unchecked] s.setValue("inactive", inactive) def populate(self): """Populate the tree view with data from the installed extensions. """ # TODO/Question: # Would it make sense to move this to a dedicated model class # complementing the FailedModel? root = self.tree.model().invisibleRootItem() extensions = app.extensions() for ext in extensions.installed_extensions(): ext_infos = extensions.infos(ext) display_name = ext_infos.get(ext, ext) if ext_infos else ext.name() loaded_extension = extensions.get(ext) if loaded_extension: display_name += ' ({})'.format(loaded_extension.load_time()) name_item = QStandardItem(display_name) name_item.extension_name = ext name_item.setCheckable(True) self.name_items[ext] = name_item icon = extensions.icon(ext) if icon: name_item.setIcon(icon) root.appendRow([name_item]) for entry in [ 'extension-name', 'short-description', 'description', 'version', 'api-version', 'dependencies', 'maintainers', 'repository', 'website', 'license' ]: label_item = QStandardItem('{}:'.format( self.config_labels[entry])) label_item.setTextAlignment(Qt.AlignTop) bold = QFont() bold.setWeight(QFont.Bold) label_item.setFont(bold) details = ext_infos.get(entry, "") if ext_infos else "" if type(details) == list: details = '\n'.join(details) details_item = QStandardItem(details) details_item.setTextAlignment(Qt.AlignTop) if entry == 'api-version': # Check for correct(ly formatted) api-version entry # and highlight it in case of mismatch api_version = appinfo.extension_api if not details: details_item.setFont(bold) details_item.setText( _("Misformat: {api}").format(details)) elif not details == api_version: details_item.setFont(bold) details_item.setText('{} ({}: {})'.format( details, appinfo.appname, api_version)) name_item.appendRow([label_item, details_item]) def selected_extension(self): """Return the (directory) name of the extension that is currently selected.""" return self._selected_extension def selection_changed(self, new, old): """Show the configuration widget for the selected extension, if available.""" config = self.siblingGroup(Config) if new.indexes(): ext_item = self.tree.model().itemFromIndex(new.indexes()[0]) # NOTE: This may have to be changed if there should be # more complexity in the tree model than now (a selected # row is either a top-level row or its immediate child) if not hasattr(ext_item, 'extension_name'): ext_item = ext_item.parent() name = ext_item.extension_name if name == self.selected_extension(): return else: # If nothing is selected, show the "empty" widget name = "" config.hide_extension() self._selected_extension = name config.show_extension(name)
class Dialog_ImageFolder(): def __init__(self, parent, title, init_path): self.w = QDialog(parent) self.parent = parent self.left = 300 self.top = 300 self.width = 600 self.height = 400 self.title = title self.dirModel = QFileSystemModel() self.dirModel.setRootPath(init_path) self.dirModel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs) self.treeview = QTreeView() self.treeview.setModel(self.dirModel) self.treeview.setRootIndex(self.dirModel.index("")) self.treeview.clicked.connect(self.on_clicked) #--- Hide All Header Sections Except First ---- header = self.treeview.header() for sec in range(1, header.count()): header.setSectionHidden(sec, True) #--- ---- ---- ---- ---- ---- ---- ---- ---- -- focus_index = self.dirModel.index(init_path) self.treeview.setCurrentIndex(focus_index) self.current_row_changed() self.listview = QListView() self.listview.setViewMode(QListView.IconMode) self.listview.setIconSize(QSize(192, 192)) targetfiles1 = glob.glob(os.path.join(init_path, '*.png')) targetfiles2 = glob.glob(os.path.join(init_path, '*.tif')) targetfiles3 = glob.glob(os.path.join(init_path, '*.tiff')) targetfiles = targetfiles1 + targetfiles2 + targetfiles3 lm = _MyListModel(targetfiles, self.parent) self.listview.setModel(lm) self.sub_layout = QHBoxLayout() self.sub_layout.addWidget(self.treeview) self.sub_layout.addWidget(self.listview) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.main_layout = QVBoxLayout() self.main_layout.addLayout(self.sub_layout) self.main_layout.addWidget(self.buttonBox) self.w.setGeometry(self.left, self.top, self.width, self.height) self.w.setWindowTitle(self.title) self.w.setWindowIcon(QIcon(os.path.join(icon_dir, 'Mojo2_16.png'))) self.w.setLayout(self.main_layout) def current_row_changed(self): index = self.treeview.currentIndex() self.treeview.scrollTo(index, QAbstractItemView.EnsureVisible) self.treeview.resizeColumnToContents(0) def on_clicked(self, index): path = self.dirModel.fileInfo(index).absoluteFilePath() targetfiles1 = glob.glob(os.path.join(path, '*.png')) targetfiles2 = glob.glob(os.path.join(path, '*.tif')) targetfiles3 = glob.glob(os.path.join(path, '*.tiff')) targetfiles = targetfiles1 + targetfiles2 + targetfiles3 lm = _MyListModel(targetfiles, self.parent) self.listview.setModel(lm) def accept(self): index = self.treeview.currentIndex() self.newdir = self.dirModel.filePath(index) self.w.done(1) def reject(self): self.w.done(0) def GetValue(self): index = self.treeview.currentIndex() self.newdir = self.dirModel.filePath(index) return self.newdir
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.setMinimumSize(QSize(480, 80)) self.setWindowTitle("PyQtSample") central_widget = QWidget(self) self.setCentralWidget(central_widget) hbox_layout = QHBoxLayout(self) central_widget.setLayout(hbox_layout) buttons_background = QWidget(self) buttons_layout = QHBoxLayout() buttons_background.setLayout(buttons_layout) self.add_button = QPushButton("Add") buttons_layout.addWidget(self.add_button) self.remove_button = QPushButton("Remove") buttons_layout.addWidget(self.remove_button) tree_layout = QVBoxLayout(self) self.tree_view = QTreeView() self.tree_view.header().hide() self.tree_view.setMaximumWidth(300) self.tree_model = ObjectsModel.TreeModel() self.tree_view.setModel(self.tree_model) tree_layout.addWidget(buttons_background) tree_layout.addWidget(self.tree_view) hbox_layout.addLayout(tree_layout) self.graphics_view = QGraphicsView() self.scene = QGraphicsScene() self.graphics_view.setScene(self.scene) hbox_layout.addWidget(self.graphics_view) self.properties_view = QTableView() self.properties_view.setMaximumWidth(300) self.properties_model = PropertiesModel.TableModel() self.properties_view.setModel(self.properties_model) hbox_layout.addWidget(self.properties_view) self.init_menu() self.test() self.connectSignals() def connectSignals(self): self.add_button.clicked.connect(self.onAddClicked) self.remove_button.clicked.connect(self.onRemoveClicked) self.tree_view.clicked.connect(self.onClicked) self.properties_model.dataChanged.connect(self.onPropertyChanged) def onPropertyChanged(self): self.rebuildModel() def rebuildModel(self): #save index hierarhy indexes = [] tmp_index = self.tree_view.currentIndex() indexes.append(tmp_index) while tmp_index.parent().isValid(): indexes.append(tmp_index.parent()) tmp_index = tmp_index.parent() self.tree_model.initRoot(self.items) self.tree_view.expandAll() if len(indexes) == 0: self.onClicked(self.tree_model.index(0, 0)) else: last_index = indexes.pop(-1) index = self.tree_model.index(last_index.row(), last_index.column()) #restore index hierarchy while len(indexes) > 0: last_index = indexes.pop(-1) index = self.tree_model.index(last_index.row(), last_index.column(), index) if index.isValid(): self.onClicked(index) else: self.onClicked(self.tree_model.index(0, 0)) def init_menu(self): exit_action = QAction("&Exit", self) exit_action.setShortcut('Ctrl+Q') exit_action.triggered.connect(qApp.quit) file_menu = self.menuBar().addMenu("&File") file_menu.addAction(QAction("Open", self)) file_menu.addAction(QAction("Save", self)) file_menu.addAction(QAction("SaveAs", self)) file_menu.addSeparator() file_menu.addAction(exit_action) def appendObjectOnScene(self, object=DataStructures.Object): if object.rect.isValid(): self.scene.addRect(object.rect, object.color) for child in object.childrens: self.appendObjectOnScene(child) def onClicked(self, index): object = index.data(Qt.UserRole + 1) self.properties_model.initProperties(object) self.scene.clear() self.appendObjectOnScene(object) self.tree_view.setCurrentIndex(index) def onAddClicked(self): index = self.tree_view.currentIndex() print(index) object = index.data(Qt.UserRole + 1) print(object.description()) object.add_children(DataStructures.Object("New item")) self.rebuildModel() def onRemoveClicked(self): index = self.tree_view.currentIndex() object = index.data(Qt.UserRole + 1) if not object.parent: self.items.remove(object) else: object.parent.childrens.remove(object) self.rebuildModel() def test(self): self.items = [] static = DataStructures.Object( "Static", DataStructures.createRect(0, 0, 800, 200)) static.add_children(DataStructures.Object("child_1")) static.add_children(DataStructures.Object("child_2")) static.add_children(DataStructures.Object("child_3")) static.color = QColor(200, 0, 0).name() static.childrens[0].add_children( DataStructures.Object("child_1.1", DataStructures.createRect(40, 40, 80, 40))) self.items.append(static) dynamic = DataStructures.Object( "Dynamic", DataStructures.createRect(0, 0, 200, 800)) dynamic.add_children(DataStructures.Object("child_1")) dynamic.add_children(DataStructures.Object("child_2")) dynamic.add_children(DataStructures.Object("child_3")) dynamic.childrens[2].add_children(DataStructures.Object("child_2.1")) dynamic.color = QColor(0, 0, 200).name() self.items.append(dynamic) self.rebuildModel()
class mainform(QWidget): """ Главное окно, оно будет пока и главным классом приложения """ def __init__(self): QWidget.__init__(self) self.resize(600, 400) self.connect() #запущаем соединение def connect(self): #http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html #http://ftp.ics.uci.edu/pub/centos0/ics-custom-build/BUILD/PyQt-x11-gpl-4.7.2/examples/itemviews/simpletreemodel/simpletreemodel.py con = qsq.QSqlDatabase.addDatabase("QSQLITE", 'Base') # делаем подключение к БД con.setDatabaseName("fn.sqlite") # устанавливаем имя базы if not con.open(): # если не открылось print ("База данных не открылась!") print ("-"+con.lastError().text()+"-") print (str(con.lastError().type())) return # cur = qsq.QSqlQuery(con) # это прямое открытие # cur.exec("SELECT * FROM cases") # print (cur.lastError().text()) #self.view = QTableView() # создаём табличный вид self.view = QTreeView() self.model2 = TableToTreeModel2(self, con) # создаём модельку - стандартную для БД, табличную self.view.setModel(self.model2) # устанавливаем модель для вида self.model2.setTable("cases") # устанавливаем таблицу и селектим из неё всё self.model2.select() # вот на этом этапе модель заполняется данными self.view.header().moveSection(11,1) self.view.header().moveSection(12,2) self.view.header().moveSection(13,3) self.view.hideColumn(1) self.view.hideColumn(6) self.view.hideColumn(7) self.view.hideColumn(8) #self.view.hideColumn(9) self.view.header().hideSection(9) # # print (self.model2.rowCount()) # возвращает количество строк # for i in range (self.model2.rowCount()): # print (self.model2.data ( self.model2.index(i,1) )) #self.model2.setFilter('_id>1') # установка фильтра на селект #self.model2.setFilter('') # и снятие оного #print (self.model2.record(0).value('_shortText')) # так можно получить данные #print (self.model2.index(0,0)) # print (self.model2.data ( self.model2.index(0,0) )) self.layout = QVBoxLayout() # пихаем вид в интерфейс self.layout.addWidget(self.view) self.setLayout(self.layout) con.close() # закрываем соединение
def _unittest_register_tree_model(): import gc from PyQt5.QtWidgets import ( QApplication, QMainWindow, QTreeView, QHeaderView, QStyleOptionViewItem, ) from ._mock_registers import get_mock_registers from .editor_delegate import EditorDelegate from .style_option_modifying_delegate import StyleOptionModifyingDelegate app = QApplication([]) win = QMainWindow() tw = QTreeView(win) tw.setItemDelegate(EditorDelegate(tw, lambda s: print("Editor display:", s))) tw.setItemDelegateForColumn( 0, StyleOptionModifyingDelegate( tw, decoration_position=QStyleOptionViewItem.Right ), ) tw.setStyleSheet( """ QTreeView::item { padding: 0 5px; } """ ) header: QHeaderView = tw.header() header.setSectionResizeMode(QHeaderView.ResizeToContents) header.setStretchLastSection( False ) # Horizontal scroll bar doesn't work if this is enabled registers = get_mock_registers() for r in registers: assert r.update_event.num_handlers == 0 model = Model(win, registers) tw.setModel(model) for r in registers: assert r.update_event.num_handlers == 1 win.setCentralWidget(tw) win.show() good_night_sweet_prince = False async def run_events(): while not good_night_sweet_prince: app.processEvents() await asyncio.sleep(0.01) async def walk(): nonlocal good_night_sweet_prince await asyncio.sleep(5) good_night_sweet_prince = True asyncio.get_event_loop().run_until_complete(asyncio.gather(run_events(), walk())) win.close() # At the very end, making sure that our registers do not keep the model alive via weak callback references del tw del win del app gc.collect() print("Model references:", gc.get_referrers(model)) del model gc.collect() for r in registers: assert r.update_event.num_handlers == 1 r.update_event.emit(r) assert r.update_event.num_handlers == 0
class AddBookmarkDialog(QDialog, Ui_AddBookmarkDialog): """ Class implementing a dialog to add a bookmark or a bookmark folder. """ def __init__(self, parent=None, bookmarksManager=None): """ Constructor @param parent reference to the parent widget (QWidget) @param bookmarksManager reference to the bookmarks manager object (BookmarksManager) """ super(AddBookmarkDialog, self).__init__(parent) self.setupUi(self) self.__bookmarksManager = bookmarksManager self.__addedNode = None self.__addFolder = False if self.__bookmarksManager is None: import Helpviewer.HelpWindow self.__bookmarksManager = \ Helpviewer.HelpWindow.HelpWindow.bookmarksManager() self.__proxyModel = AddBookmarkProxyModel(self) model = self.__bookmarksManager.bookmarksModel() self.__proxyModel.setSourceModel(model) self.__treeView = QTreeView(self) self.__treeView.setModel(self.__proxyModel) self.__treeView.expandAll() self.__treeView.header().setStretchLastSection(True) self.__treeView.header().hide() self.__treeView.setItemsExpandable(False) self.__treeView.setRootIsDecorated(False) self.__treeView.setIndentation(10) self.__treeView.show() self.locationCombo.setModel(self.__proxyModel) self.locationCombo.setView(self.__treeView) self.addressEdit.setInactiveText(self.tr("Url")) self.nameEdit.setInactiveText(self.tr("Title")) self.resize(self.sizeHint()) def setUrl(self, url): """ Public slot to set the URL of the new bookmark. @param url URL of the bookmark (string) """ self.addressEdit.setText(url) self.resize(self.sizeHint()) def url(self): """ Public method to get the URL of the bookmark. @return URL of the bookmark (string) """ return self.addressEdit.text() def setTitle(self, title): """ Public method to set the title of the new bookmark. @param title title of the bookmark (string) """ self.nameEdit.setText(title) def title(self): """ Public method to get the title of the bookmark. @return title of the bookmark (string) """ return self.nameEdit.text() def setDescription(self, description): """ Public method to set the description of the new bookmark. @param description description of the bookamrk (string) """ self.descriptionEdit.setPlainText(description) def description(self): """ Public method to get the description of the bookmark. @return description of the bookamrk (string) """ return self.descriptionEdit.toPlainText() def setCurrentIndex(self, idx): """ Public method to set the current index. @param idx current index to be set (QModelIndex) """ proxyIndex = self.__proxyModel.mapFromSource(idx) self.__treeView.setCurrentIndex(proxyIndex) self.locationCombo.setCurrentIndex(proxyIndex.row()) def currentIndex(self): """ Public method to get the current index. @return current index (QModelIndex) """ idx = self.locationCombo.view().currentIndex() idx = self.__proxyModel.mapToSource(idx) return idx def setFolder(self, folder): """ Public method to set the dialog to "Add Folder" mode. @param folder flag indicating "Add Folder" mode (boolean) """ self.__addFolder = folder if folder: self.setWindowTitle(self.tr("Add Folder")) self.addressEdit.setVisible(False) else: self.setWindowTitle(self.tr("Add Bookmark")) self.addressEdit.setVisible(True) self.resize(self.sizeHint()) def isFolder(self): """ Public method to test, if the dialog is in "Add Folder" mode. @return flag indicating "Add Folder" mode (boolean) """ return self.__addFolder def addedNode(self): """ Public method to get a reference to the added bookmark node. @return reference to the added bookmark node (BookmarkNode) """ return self.__addedNode def accept(self): """ Public slot handling the acceptance of the dialog. """ if (not self.__addFolder and not self.addressEdit.text()) or \ not self.nameEdit.text(): super(AddBookmarkDialog, self).accept() return from .BookmarkNode import BookmarkNode idx = self.currentIndex() if not idx.isValid(): idx = self.__bookmarksManager.bookmarksModel().index(0, 0) parent = self.__bookmarksManager.bookmarksModel().node(idx) if self.__addFolder: type_ = BookmarkNode.Folder else: type_ = BookmarkNode.Bookmark bookmark = BookmarkNode(type_) bookmark.title = self.nameEdit.text() if not self.__addFolder: bookmark.url = self.addressEdit.text() bookmark.desc = self.descriptionEdit.toPlainText() self.__bookmarksManager.addBookmark(parent, bookmark) self.__addedNode = bookmark super(AddBookmarkDialog, self).accept()
class ChessClaimView(QMainWindow): """ The main window of the application. Attributes: rowCount(int): The number of the row the TreeView Table has. iconsSize(int): The recommended size of the icons. mac_notification: Notification for macOS win_notification: Notification for windows OS """ def __init__(self): super().__init__() self.resize(720, 275) self.iconsSize = 16 self.setWindowTitle('Chess Claim Tool') self.center() self.rowCount = 0 if (platform.system() == "Darwin"): from MacNotification import Notification self.mac_notification = Notification() elif (platform.system() == "Windows"): from win10toast import ToastNotifier self.win_notification = ToastNotifier() def center(self): """ Centers the window on the screen """ screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2) def set_GUI(self): """ Initialize GUI components. """ # Create the Menu self.livePgnOption = QAction('Live PGN',self) self.livePgnOption.setCheckable(True) aboutAction = QAction('About',self) menubar = self.menuBar() optionsMenu = menubar.addMenu('&Options') optionsMenu.addAction(self.livePgnOption) aboutMenu = menubar.addMenu('&Help') aboutMenu.addAction(aboutAction) aboutAction.triggered.connect(self.slots.on_about_clicked) # Create the Claims Table (TreeView) self.claimsTable = QTreeView() self.claimsTable.setFocusPolicy(Qt.NoFocus) self.claimsTable.setEditTriggers(QAbstractItemView.NoEditTriggers) self.claimsTable.header().setDefaultAlignment(Qt.AlignCenter) self.claimsTable.setSortingEnabled(True) self.claimsTable.doubleClicked.connect(self.open_game) # Create the Claims Model self.claimsTableModel = QStandardItemModel() labels = ["#","Timestamp","Type","Board","Players","Move"] self.claimsTableModel.setHorizontalHeaderLabels(labels) self.claimsTable.setModel(self.claimsTableModel) # Create the Scan & Stop Button Box self.buttonBox = ButtonBox(self) # Create the Sources Button sourcesButton = QPushButton("Add Sources") sourcesButton.setObjectName("sources") sourcesButton.clicked.connect(self.slots.on_sourcesButton_clicked) # Create the Status Bar self.pixmapCheck = QPixmap(resource_path("check_icon.png")) self.pixmapError = QPixmap(resource_path("error_icon.png")) self.sourceLabel = QLabel() self.sourceLabel.setObjectName("source-label") self.sourceImage = QLabel() self.sourceImage.setObjectName("source-image") self.downloadLabel = QLabel() self.downloadLabel.setObjectName("download-label") self.downloadImage = QLabel() self.downloadImage.setObjectName("download-image") self.scanningLabel = QLabel() self.scanningLabel.setObjectName("scanning") self.spinnerLabel = QLabel() self.spinnerLabel.setVisible(False) self.spinnerLabel.setObjectName("spinner") self.spinner = QMovie(resource_path("spinner.gif")) self.spinner.setScaledSize(QSize(self.iconsSize, self.iconsSize)) self.spinnerLabel.setMovie(self.spinner) self.spinner.start() self.statusBar = QStatusBar() self.statusBar.setSizeGripEnabled(False) self.statusBar.addWidget(self.sourceLabel) self.statusBar.addWidget(self.sourceImage) self.statusBar.addWidget(self.downloadLabel) self.statusBar.addWidget(self.downloadImage) self.statusBar.addWidget(self.scanningLabel) self.statusBar.addWidget(self.spinnerLabel) self.statusBar.addPermanentWidget(sourcesButton) self.statusBar.setContentsMargins(10,5,9,5) # Container Layout for the Central Widget containerLayout = QVBoxLayout() containerLayout.setSpacing(0) containerLayout.addWidget(self.claimsTable) containerLayout.addWidget(self.buttonBox) # Central Widget containerWidget = QWidget() containerWidget.setLayout(containerLayout) self.setCentralWidget(containerWidget) self.setStatusBar(self.statusBar) def open_game(self): """ TODO: Double click should open a window to replay the game.""" pass def resize_claimsTable(self): """ Resize the table (if needed) after the insertion of a new element""" for index in range(0,6): self.claimsTable.resizeColumnToContents(index) def set_slots(self, slots): """ Connect the Slots """ self.slots = slots def add_to_table(self,type,bo_number,players,move): """ Add new row to the claimsTable Args: type: The type of the draw (3 Fold Repetition, 5 Fold Repetition, 50 Moves Rule, 75 Moves Rule). bo_number: The number of the boards, if this information is available. players: The name of the players. move: With which move the draw is valid. """ # Before insertion, remove rows as descripted in the remove_rows function self.remove_rows(type,players) timestamp = str(datetime.now().strftime('%H:%M:%S')) row = [] items = [str(self.rowCount+1),timestamp,type,bo_number,players,move] """ Convert each item(str) to QStandardItem, make the necessary stylistic additions and append it to row.""" for index in range(len(items)): standardItem = QStandardItem(items[index]) standardItem.setTextAlignment(Qt.AlignCenter) if(index == 2): font = standardItem.font() font.setBold(True) standardItem.setFont(font) if (items[index] == "5 Fold Repetition" or items[index] == "75 Moves Rule"): standardItem.setData(QColor(255,0,0), Qt.ForegroundRole) row.append(standardItem) self.claimsTableModel.appendRow(row) self.rowCount = self.rowCount+1 # After the insertion resize the table self.resize_claimsTable() # Always the last row(the bottom of the table) should be visible. self.claimsTable.scrollToBottom() #Send Notification self.notify(type,players,move) def notify(self,type,players,move): """ Send notification depending on the OS. Args: type: The type of the draw (3 Fold Repetition, 5 Fold Repetition, 50 Moves Rule, 75 Moves Rule). players: The names of the players. move: With which move the draw is valid. """ if (platform.system() == "Darwin"): self.mac_notification.clearNotifications() self.mac_notification.notify(type,players,move) elif(platform.system() == "Windows"): self.win_notification.show_toast(type, players+"\n"+move, icon_path=resource_path("logo.ico"), duration=5, threaded=True) def remove_from_table(self,index): """ Remove element from the claimsTable. Args: index: The index of the row we want to remove. First row has index=0. """ self.claimsTableModel.removeRow(index) def remove_rows(self,type,players): """ Removes a existing row from the Claims Table when same players made the same type of draw with a new move - or they made 5 Fold Repetition over the 3 Fold or 75 Moves Rule over 50 moves Rule. Args: type: The type of the draw (3 Fold Repetition, 5 Fold Repetition, 50 Moves Rule, 75 Moves Rule). players: The names of the players. """ for index in range(self.rowCount): try: modelType = self.claimsTableModel.item(index,2).text() modelPlayers = self.claimsTableModel.item(index,4).text() except AttributeError: modelType = "" modelPlayers = "" if (modelType == type and modelPlayers == players): self.remove_from_table(index) self.rowCount = self.rowCount - 1 break elif (type == "5 Fold Repetition" and modelType == "3 Fold Repetition" and modelPlayers == players) : self.remove_from_table(index) self.rowCount = self.rowCount - 1 break elif (type == "75 Moves Rule" and modelType == "50 Moves Rule" and modelPlayers == players): self.remove_from_table(index) self.rowCount = self.rowCount - 1 break def clear_table(self): """ Clear all the elements off the Claims Table and resets the rowCount. """ for index in range(self.rowCount): self.claimsTableModel.removeRow(0) self.rowCount = 0 def set_sources_status(self,status,validSources=None): """ Adds the sourcess in the statusBar. Args: status(str): The status of the validity of the sources. "ok": At least one source is valid. "error": None of the sources are valid. validSources(list): The list of valid sources, if there is any. This list is used here to display the ToolTip. """ self.sourceLabel.setText("Sources:") # Set the ToolTip if there are sources. try: text = "" for index in range(len(validSources)): if (index == len(validSources) - 1): number = str(index+1) text = text+number+") "+validSources[index].get_value() else: number = str(index+1) text = text+number+") "+validSources[index].get_value()+"\n" self.sourceLabel.setToolTip(text) except TypeError: pass if (status == "ok"): self.sourceImage.setPixmap(self.pixmapCheck.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) else: self.sourceImage.setPixmap(self.pixmapError.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) def set_download_status(self,status): """ Adds download status in the statusBar. Args: status(str): The status of the download(s). "ok": The download of the sources is successful. "error": The download of the sources failed. "stop": The download process stopped. """ timestamp = str(datetime.now().strftime('%H:%M:%S')) self.downloadLabel.setText(timestamp+" Download:") if (status == "ok"): self.downloadImage.setPixmap(self.pixmapCheck.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) elif (status == "error"): self.downloadImage.setPixmap(self.pixmapError.scaled(self.iconsSize,self.iconsSize,transformMode=Qt.SmoothTransformation)) elif (status == "stop"): self.downloadImage.clear() self.downloadLabel.clear() def set_scan_status(self,status): """ Adds the scan status in the statusBar. Args: status(str): The status of the scan process. "active": The scan process is active. "error": The scan process waits for a new file. "stop": The scan process stopped. """ if (status == "wait"): self.scanningLabel.setText("Scan: Waiting") self.spinnerLabel.setVisible(False) elif (status == "active"): self.scanningLabel.setText("Scanning...") self.spinnerLabel.setVisible(True) elif (status == "stop"): self.scanningLabel.clear() self.spinnerLabel.setVisible(False) def change_scanButton_text(self,status): """ Changes the text of the scanButton depending on the status of the application. Args: status(str): The status of the scan process. "active": The scan process is active. "wait": The scan process is being terminated "stop": The scan process stopped. """ if (status == "active"): self.buttonBox.scanButton.setText("Scanning PGN...") elif (status == "stop"): self.buttonBox.scanButton.setText("Start Scan") elif(status == "wait"): self.buttonBox.scanButton.setText("Please Wait") def enable_buttons(self): self.buttonBox.scanButton.setEnabled(True) self.buttonBox.stopButton.setEnabled(True) def disable_buttons(self): self.buttonBox.scanButton.setEnabled(False) self.buttonBox.stopButton.setEnabled(False) def enable_statusBar(self): """ Show download and scan status messages - if they were previously hidden (by disable_statusBar) - from the statusBar.""" self.downloadLabel.setVisible(True) self.scanningLabel.setVisible(True) self.downloadImage.setVisible(True) def disable_statusBar(self): """ Hide download and scan status messages from the statusBar. """ self.downloadLabel.setVisible(False) self.downloadImage.setVisible(False) self.scanningLabel.setVisible(False) self.spinnerLabel.setVisible(False) def closeEvent(self,event): """ Reimplement the close button If the program is actively scanning a pgn a warning dialog shall be raised in order to make sure that the user didn't clicked the close Button accidentally. Args: event: The exit QEvent. """ try: if (self.slots.scanWorker.isRunning): exitDialog = QMessageBox() exitDialog.setWindowTitle("Warning") exitDialog.setText("Scanning in Progress") exitDialog.setInformativeText("Do you want to quit?") exitDialog.setIcon(exitDialog.Warning) exitDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) exitDialog.setDefaultButton(QMessageBox.Cancel) replay = exitDialog.exec() if replay == QMessageBox.Yes: event.accept() else: event.ignore() except: event.accept() def load_warning(self): """ Displays a Warning Dialog. trigger: User clicked the "Start Scanning" Button without any valid pgn source. """ warningDialog = QMessageBox() warningDialog.setIcon(warningDialog.Warning) warningDialog.setWindowTitle("Warning") warningDialog.setText("PGN File(s) Not Found") warningDialog.setInformativeText("Please enter at least one valid PGN source.") warningDialog.exec() def load_about_dialog(self): """ Displays the About Dialog.""" self.aboutDialog = AboutDialog() self.aboutDialog.set_GUI() self.aboutDialog.show()
class DirectoriesDialog(QMainWindow): def __init__(self, app, **kwargs): super().__init__(None, **kwargs) self.app = app self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS self.recentFolders = Recent(self.app, 'recentFolders') self._setupUi() self._updateScanTypeList() self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView) self.directoriesDelegate = DirectoriesDelegate() self.treeView.setItemDelegate(self.directoriesDelegate) self._setupColumns() self.app.recentResults.addMenu(self.menuLoadRecent) self.app.recentResults.addMenu(self.menuRecentResults) self.recentFolders.addMenu(self.menuRecentFolders) self._updateAddButton() self._updateRemoveButton() self._updateLoadResultsButton() self._updateActionsState() self._setupBindings() def _setupBindings(self): self.appModeRadioBox.itemSelected.connect(self.appModeButtonSelected) self.showPreferencesButton.clicked.connect(self.app.actionPreferences.trigger) self.scanButton.clicked.connect(self.scanButtonClicked) self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger) self.addFolderButton.clicked.connect(self.actionAddFolder.trigger) self.removeFolderButton.clicked.connect(self.removeFolderButtonClicked) self.treeView.selectionModel().selectionChanged.connect(self.selectionChanged) self.app.recentResults.itemsChanged.connect(self._updateLoadResultsButton) self.recentFolders.itemsChanged.connect(self._updateAddButton) self.recentFolders.mustOpenItem.connect(self.app.model.add_directory) self.directoriesModel.foldersAdded.connect(self.directoriesModelAddedFolders) self.app.willSavePrefs.connect(self.appWillSavePrefs) def _setupActions(self): # (name, shortcut, icon, desc, func) ACTIONS = [ ('actionLoadResults', 'Ctrl+L', '', tr("Load Results..."), self.loadResultsTriggered), ('actionShowResultsWindow', '', '', tr("Results Window"), self.app.showResultsWindow), ('actionAddFolder', '', '', tr("Add Folder..."), self.addFolderTriggered), ] createActions(ACTIONS, self) def _setupMenu(self): self.menubar = QMenuBar(self) self.menubar.setGeometry(QRect(0, 0, 42, 22)) self.menuFile = QMenu(self.menubar) self.menuFile.setTitle(tr("File")) self.menuView = QMenu(self.menubar) self.menuView.setTitle(tr("View")) self.menuHelp = QMenu(self.menubar) self.menuHelp.setTitle(tr("Help")) self.menuLoadRecent = QMenu(self.menuFile) self.menuLoadRecent.setTitle(tr("Load Recent Results")) self.setMenuBar(self.menubar) self.menuFile.addAction(self.actionLoadResults) self.menuFile.addAction(self.menuLoadRecent.menuAction()) self.menuFile.addSeparator() self.menuFile.addAction(self.app.actionClearPictureCache) self.menuFile.addSeparator() self.menuFile.addAction(self.app.actionQuit) self.menuView.addAction(self.app.actionPreferences) self.menuView.addAction(self.actionShowResultsWindow) self.menuView.addAction(self.app.actionIgnoreList) self.menuHelp.addAction(self.app.actionShowHelp) self.menuHelp.addAction(self.app.actionOpenDebugLog) self.menuHelp.addAction(self.app.actionAbout) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) # Recent folders menu self.menuRecentFolders = QMenu() self.menuRecentFolders.addAction(self.actionAddFolder) self.menuRecentFolders.addSeparator() # Recent results menu self.menuRecentResults = QMenu() self.menuRecentResults.addAction(self.actionLoadResults) self.menuRecentResults.addSeparator() def _setupUi(self): self.setWindowTitle(self.app.NAME) self.resize(420, 338) self.centralwidget = QWidget(self) self.verticalLayout = QVBoxLayout(self.centralwidget) hl = QHBoxLayout() label = QLabel(tr("Application Mode:"), self) label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(label) self.appModeRadioBox = RadioBox( self, items=[tr("Standard"), tr("Music"), tr("Picture")], spread=False ) hl.addWidget(self.appModeRadioBox) self.verticalLayout.addLayout(hl) hl = QHBoxLayout() hl.setAlignment(Qt.AlignLeft) label = QLabel(tr("Scan Type:"), self) label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(label) self.scanTypeComboBox = QComboBox(self) self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.scanTypeComboBox.setMaximumWidth(400) hl.addWidget(self.scanTypeComboBox) self.showPreferencesButton = QPushButton(tr("More Options"), self.centralwidget) self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(self.showPreferencesButton) self.verticalLayout.addLayout(hl) self.promptLabel = QLabel(tr("Select folders to scan and press \"Scan\"."), self.centralwidget) self.verticalLayout.addWidget(self.promptLabel) self.treeView = QTreeView(self.centralwidget) self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection) self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows) self.treeView.setAcceptDrops(True) triggers = QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed\ | QAbstractItemView.SelectedClicked self.treeView.setEditTriggers(triggers) self.treeView.setDragDropOverwriteMode(True) self.treeView.setDragDropMode(QAbstractItemView.DropOnly) self.treeView.setUniformRowHeights(True) self.verticalLayout.addWidget(self.treeView) self.horizontalLayout = QHBoxLayout() self.removeFolderButton = QPushButton(self.centralwidget) self.removeFolderButton.setIcon(QIcon(QPixmap(":/minus"))) self.removeFolderButton.setShortcut("Del") self.horizontalLayout.addWidget(self.removeFolderButton) self.addFolderButton = QPushButton(self.centralwidget) self.addFolderButton.setIcon(QIcon(QPixmap(":/plus"))) self.horizontalLayout.addWidget(self.addFolderButton) spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.loadResultsButton = QPushButton(self.centralwidget) self.loadResultsButton.setText(tr("Load Results")) self.horizontalLayout.addWidget(self.loadResultsButton) self.scanButton = QPushButton(self.centralwidget) self.scanButton.setText(tr("Scan")) self.scanButton.setDefault(True) self.horizontalLayout.addWidget(self.scanButton) self.verticalLayout.addLayout(self.horizontalLayout) self.setCentralWidget(self.centralwidget) self._setupActions() self._setupMenu() if self.app.prefs.directoriesWindowRect is not None: self.setGeometry(self.app.prefs.directoriesWindowRect) else: moveToScreenCenter(self) def _setupColumns(self): header = self.treeView.header() header.setStretchLastSection(False) header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.Fixed) header.resizeSection(1, 100) def _updateActionsState(self): self.actionShowResultsWindow.setEnabled(self.app.resultWindow is not None) def _updateAddButton(self): if self.recentFolders.isEmpty(): self.addFolderButton.setMenu(None) else: self.addFolderButton.setMenu(self.menuRecentFolders) def _updateRemoveButton(self): indexes = self.treeView.selectedIndexes() if not indexes: self.removeFolderButton.setEnabled(False) return self.removeFolderButton.setEnabled(True) def _updateLoadResultsButton(self): if self.app.recentResults.isEmpty(): self.loadResultsButton.setMenu(None) else: self.loadResultsButton.setMenu(self.menuRecentResults) def _updateScanTypeList(self): try: self.scanTypeComboBox.currentIndexChanged[int].disconnect(self.scanTypeChanged) except TypeError: # Not connected, ignore pass self.scanTypeComboBox.clear() scan_options = self.app.model.SCANNER_CLASS.get_scan_options() for scan_option in scan_options: self.scanTypeComboBox.addItem(scan_option.label) SCAN_TYPE_ORDER = [so.scan_type for so in scan_options] selected_scan_type = self.app.prefs.get_scan_type(self.app.model.app_mode) scan_type_index = SCAN_TYPE_ORDER.index(selected_scan_type) self.scanTypeComboBox.setCurrentIndex(scan_type_index) self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged) self.app._update_options() #--- QWidget overrides def closeEvent(self, event): event.accept() if self.app.model.results.is_modified: title = tr("Unsaved results") msg = tr("You have unsaved results, do you really want to quit?") if not self.app.confirm(title, msg): event.ignore() if event.isAccepted(): QApplication.quit() #--- Events def addFolderTriggered(self): title = tr("Select a folder to add to the scanning list") flags = QFileDialog.ShowDirsOnly dirpath = str(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags)) if not dirpath: return self.lastAddedFolder = dirpath self.app.model.add_directory(dirpath) self.recentFolders.insertItem(dirpath) def appModeButtonSelected(self, index): if index == 2: mode = AppMode.Picture elif index == 1: mode = AppMode.Music else: mode = AppMode.Standard self.app.model.app_mode = mode self._updateScanTypeList() def appWillSavePrefs(self): self.app.prefs.directoriesWindowRect = self.geometry() def directoriesModelAddedFolders(self, folders): for folder in folders: self.recentFolders.insertItem(folder) def loadResultsTriggered(self): title = tr("Select a results file to load") files = ';;'.join([tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")]) destination = QFileDialog.getOpenFileName(self, title, '', files) if destination: self.app.model.load_from(destination) self.app.recentResults.insertItem(destination) def removeFolderButtonClicked(self): self.directoriesModel.model.remove_selected() def scanButtonClicked(self): if self.app.model.results.is_modified: title = tr("Start a new scan") msg = tr("You have unsaved results, do you really want to continue?") if not self.app.confirm(title, msg): return self.app.model.start_scanning() def scanTypeChanged(self, index): scan_options = self.app.model.SCANNER_CLASS.get_scan_options() self.app.prefs.set_scan_type(self.app.model.app_mode, scan_options[index].scan_type) self.app._update_options() def selectionChanged(self, selected, deselected): self._updateRemoveButton()
class MainWindow(QtWidgets.QMainWindow): Folder = 1 File = 2 Table = 3 LogInfo = 101 LogWarning = 102 LogError = 103 def LoadTableData(self,data,model): try: value = json.loads(data) table = value['table'] model.setColumnCount(len(table)) data = value['data'] model.setRowCount(len(data) + 2) for v in table: model.setHeaderData(v[0],Qt.Horizontal,v[1]) model.setData(model.index(0,v[0]),v[2]) model.setData(model.index(1,v[0]),v[3]) for i in range(0,len(data)): v = data[i] for j in range(0,len(v)): model.setData(model.index(i+2,j),v[j]) model.activeColumn = value['activeColumn'] except Exception as e: pass def AddTreeItem(self,parent,text,type,isexpand = True): if parent == None: texts = text.split('.') if len(texts) > 1: rootItem = self.rootItem for i in range(0,len(texts)-1): t = texts[i] childItem = None for j in range(0,rootItem.rowCount()): childItem = rootItem.child(j,0) if t == childItem.data(): break rootItem = childItem parent = rootItem text = texts[-1] else: parent = self.rootItem lastFolderItem = None for i in range(0,parent.rowCount()): childItem = self.model.itemFromIndex(self.model.index(i,0,parent.index())) if childItem.data() == MainWindow.Folder: lastFolderItem = childItem if text == childItem.text(): return None icon = None if type == MainWindow.Folder: icon = self.iconProvider.icon(QFileIconProvider.Folder) elif type == MainWindow.File: icon = self.iconProvider.icon(QFileIconProvider.File) elif type == MainWindow.Table: icon = self.iconProvider.icon(QFileIconProvider.Desktop) item = QStandardItem(parent) item.setIcon(icon) item.setText(text) item.setData(type) item.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable) if type == MainWindow.Folder and lastFolderItem != None: parent.insertRow(lastFolderItem.row()+1,item) else: parent.appendRow(item) if isexpand == True: self.tree.expand(parent.index()) return item def SetRootTreeItem(self,text): self.rootItem = QStandardItem() self.rootItem.setIcon(self.iconProvider.icon(QFileIconProvider.Folder)) self.rootItem.setText(text) self.rootItem.setData(MainWindow.Folder) self.model.appendRow(self.rootItem) for i in range(0,self.model.columnCount()): colItem = self.model.itemFromIndex(self.model.index(0,i)) colItem.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable) def GetTreeItemShortPath(self,item): tempItem = item names = [] while True: names.append(tempItem.text()) tempItem = tempItem.parent() if tempItem == self.rootItem: break return '.'.join(reversed(names)) def OnTreeCustomContextMenuRequested(self,pt): index = self.tree.indexAt(pt); if index.isValid(): item = self.model.itemFromIndex(index) parent = item.parent() if parent != None: item = self.model.itemFromIndex(self.model.index(item.row(),0,parent.index())) def OnAddTreeItem(self,item,type): inputDialog = InputDialog.InputDialog(self) ret = inputDialog.exec_() inputDialog.destroy() if QtWidgets.QDialog.Rejected == ret: return if len(inputDialog.GetTextValue()) == 0: return itemTable = self.AddTreeItem(item,str(inputDialog.GetTextValue()),type) if MainWindow.Table == type: model = GridTableView.TableViewItemModel(2,0) model.setParent(self.tree) itemTable.setData(model,Qt.UserRole+2) cursor = None try: cursor = self.db.cursor() cursor.execute('insert into datas (k, v, t) values (\'{}\', \'{}\', {})'.format(self.GetTreeItemShortPath(itemTable),"None",type)) self.db.commit() except Exception as e: pass finally: cursor.close() def OnAddFolder(index,self = self,item = item): OnAddTreeItem(self,item,MainWindow.Folder) def OnAddFile(index,self = self,item = item): OnAddTreeItem(self,item,MainWindow.File) def OnAddTable(index,self = self,item = item): OnAddTreeItem(self,item,MainWindow.Table) def OnRename(index,self = self,item = item): inputDialog = InputDialog.InputDialog(self,item.text()) ret = inputDialog.exec_() inputDialog.destroy() if QtWidgets.QDialog.Rejected == ret: return text = inputDialog.GetTextValue() if len(text) == 0: return #old_shortpath = self.GetTreeItemShortPath(item) items = [] oldpaths = [] if item.data() == MainWindow.Table: items.append(item) else: def GetAllChildItems(items,item): for i in range(0,item.rowCount()): childItem = item.child(i,0) if childItem.data() != MainWindow.Table: GetAllChildItems(items,childItem) else: items.append(childItem) items.append(item) GetAllChildItems(items,item) for v in items: oldpaths.append(self.GetTreeItemShortPath(v)) item.setText(text) cursor = self.db.cursor() for i in range(0,len(items)): v = items[i] oldpath = oldpaths[i] cursor.execute('update datas set k=? where k=?', (self.GetTreeItemShortPath(v),oldpath)) findTabIndex = False for i in range(0,self.tabWidget.count()): if findTabIndex == True: continue if oldpath == self.tabWidget.tabToolTip(i): findTabIndex = True self.tabWidget.setTabToolTip(i,self.GetTreeItemShortPath(v)) if v == item and item.data() == MainWindow.Table: self.tabWidget.setTabText(i,text) cursor.close() self.db.commit() def OnDelete(index,self = self,item = item): if item == self.rootItem: return deleyeKeys = set() cursor = self.db.cursor() if item.data() == MainWindow.Folder or item.data() == MainWindow.File: cursor.execute('select * from datas') shortpath = self.GetTreeItemShortPath(item) for i in range(0,self.tabWidget.count()): tabText = self.tabWidget.tabToolTip(i) if len(tabText) >= len(shortpath) and tabText[0:len(shortpath)] == shortpath: self.tabWidget.removeTab(i) #if self.OnCloseTab(i) == False: # return def DeleteChildItems(cursor,item): for i in range(0,item.rowCount()): childItem = item.child(i,0) if item.data() != MainWindow.Table: cursor.execute('delete from datas where k=?', (self.GetTreeItemShortPath(childItem),)) DeleteChildItems(cursor,childItem) cursor.execute('delete from datas where k=?', (self.GetTreeItemShortPath(item),)) DeleteChildItems(cursor,item) self.model.removeRow(item.row(),item.parent().index()) elif item.data() == MainWindow.Table: shortpath = self.GetTreeItemShortPath(item) for i in range(0,self.tabWidget.count()): if self.tabWidget.tabToolTip(i) == shortpath: self.tabWidget.removeTab(i) #if self.OnCloseTab(i) == False: # return deleyeKeys.add(shortpath) self.model.removeRow(item.row(),item.parent().index()) for v in deleyeKeys: try: cursor.execute('delete from datas where k=?', (v,)) except Exception as e: pass cursor.close() self.db.commit() action_AddDir = QtWidgets.QAction("添加目录",None,triggered=OnAddFolder) action_AddConfig = QtWidgets.QAction("添加文件",None,triggered=OnAddFile) action_AddTable = QtWidgets.QAction("添加配置表",None,triggered=OnAddTable) action_Rename = QtWidgets.QAction("重命名",None,triggered=OnRename) action_Delete = QtWidgets.QAction("删除",None,triggered=OnDelete) menuTree = QtWidgets.QMenu("menuTree",self.tree) menuTree.addAction(action_AddDir) menuTree.addAction(action_AddConfig) menuTree.addAction(action_AddTable) menuTree.addSeparator() menuTree.addAction(action_Rename) menuTree.addSeparator() menuTree.addAction(action_Delete) if item == self.rootItem: action_Rename.setDisabled(True) if item.data() == MainWindow.Folder: action_AddTable.setDisabled(True) if item == self.rootItem: action_Delete.setDisabled(True) elif item.data() == MainWindow.File: action_AddDir.setDisabled(True) action_AddConfig.setDisabled(True) elif item.data() == MainWindow.Table: action_AddTable.setDisabled(True) action_AddDir.setDisabled(True) action_AddConfig.setDisabled(True) else: return menuTree.exec_(QtGui.QCursor.pos()) menuTree.destroy() def closeEvent(self, event): count = self.tabWidget.count() for i in reversed(range(0,count)): self.OnCloseTab(i) event.accept() def OnPaste(self): tableView = self.tabWidget.currentWidget() if tableView != None: if tableView.IsChanged == True: if QMessageBox.Yes == QMessageBox.information(self,'Save','Do you save changes?',QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel,QMessageBox.Yes): tableView.Save() fileName,fileType = QtWidgets.QFileDialog.getOpenFileName(self,'Open File','','Excel File(*.xls *.xlsx)') if os.path.exists(fileName) and os.path.isfile(fileName): tableView.Paste(fileName) def OnExport(self): dialog = ExportDialog.ExportDialog(self) dialog.exec_() def DoSave(self,tableView): datas = tableView.Save() if datas != None: tabIndex = self.tabWidget.indexOf(tableView) cursor = None try: cursor = self.db.cursor() k = self.tabWidget.tabToolTip(tabIndex) cursor.execute('select * from datas where k=?', (k,)) values = cursor.fetchall() if len(values) > 0 and values[0][0] == k: cursor.execute('update datas set v=? where k=?', (datas,k)) else: cursor.execute('insert into datas (k, v, t) values (\'{}\', \'{}\', {})', (k,datas,MainWindow.Table)) self.db.commit() except Exception as e: pass finally: if cursor != None: cursor.close() def OnSave(self): tableView = self.tabWidget.currentWidget() if tableView != None and tableView.IsChanged == True: self.DoSave(tableView) tabIndex = self.tabWidget.indexOf(tableView) self.tabWidget.tabBar().setTabTextColor(tabIndex,QColor(0,0,0)) def OnSaveAll(self): for i in range(0,self.tabWidget.count()): tableView = self.tabWidget.widget(i) if tableView.IsChanged == True: self.DoSave(tableView) self.tabWidget.tabBar().setTabTextColor(i,QColor(0,0,0)) def OnUndo(self): tableView = self.tabWidget.currentWidget() if tableView != None: tableView.Undo() def OnRedo(self): tableView = self.tabWidget.currentWidget() if tableView != None: tableView.Redo() def EnableSave(self,enable,tableView): if enable == True: self.tabWidget.tabBar().setTabTextColor(self.tabWidget.indexOf(tableView),QColor(233,21,10)) else: self.tabWidget.tabBar().setTabTextColor(self.tabWidget.indexOf(tableView),QColor(0,0,0)) def OnTreeDoubleClicked(self,index): if index.isValid() == False: return item = self.model.itemFromIndex(index) shortpath = self.GetTreeItemShortPath(item) findTabIndex = -1 if item.data() == MainWindow.Table: for i in range(0,self.tabWidget.count()): if self.tabWidget.tabToolTip(i) == shortpath: findTabIndex = i break if findTabIndex != -1: self.tabWidget.setCurrentIndex(findTabIndex) else: tableView = GridTableView.GridTableView(item.data(Qt.UserRole+2),self.tabWidget) tabIndex = self.tabWidget.addTab(tableView,item.text()) self.tabWidget.setTabToolTip(tabIndex,shortpath) self.tabWidget.setCurrentWidget(tableView) pass pass def OnCloseTab(self,tabId): tableView = self.tabWidget.widget(tabId) if tableView.IsChanged == True: ret = QMessageBox.information(self,'Save','Do you save changes?',QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel,QMessageBox.Yes) if QMessageBox.Yes == ret: self.DoSave(tableView) elif QMessageBox.Cancel == ret: return False self.tabWidget.removeTab(tabId) return True @property def Settings(self): if self.setting == None: self.setting = {} try: with open("Settings.cfg",'r') as f: self.setting = json.load(f) except IOError as e: pass return self.setting def GetTreeModel(self): return self.model def __del__(self): print('MainWindow.__del__') if self.db!= None: self.db.close() try: with open("Settings.cfg",'w') as f: json.dump(self.setting,f) except IOError as e: pass finally: pass pass def __init__(self): super(MainWindow, self).__init__() uic.loadUi('MainWindow.ui', self) self.db = None self.rootItem = None self.setting = None self.fileSystemWatcher = QFileSystemWatcher() self.oldWindowTitle = self.windowTitle() self.iconProvider = QFileIconProvider() splitterH = QSplitter(self.centralwidget) self.verticalLayout.addWidget(splitterH) self.tree = QTreeView(splitterH) self.model = QStandardItemModel(self.tree) self.model.setHorizontalHeaderLabels(['Name']) self.model.setColumnCount(1) self.tree.setModel(self.model) selectionModel = QItemSelectionModel(self.model) self.tree.setSelectionModel(selectionModel) self.tree.setUniformRowHeights(True) self.tree.header().setStretchLastSection(False) self.tree.viewport().setAttribute(Qt.WA_StaticContents) self.tree.setAttribute(Qt.WA_MacShowFocusRect, False) self.tree.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.tree.header().setStretchLastSection(False); self.tree.setHeaderHidden(True) self.tree.setContextMenuPolicy(Qt.CustomContextMenu) self.tree.customContextMenuRequested['QPoint'].connect(self.OnTreeCustomContextMenuRequested) self.tree.doubleClicked.connect(self.OnTreeDoubleClicked) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(1) self.tree.setSizePolicy(sizePolicy) splitterH.addWidget(self.tree) self.setStatusBar(None) self.tabWidget = QTabWidget(splitterH) self.tabWidget.setTabsClosable(True) self.tabWidget.resize(self.tabWidget.size().width(),self.size().height()/3*1) self.tabWidget.tabCloseRequested['int'].connect(self.OnCloseTab) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(7) self.tabWidget.setSizePolicy(sizePolicy) splitterH.addWidget(self.tabWidget) self.action_Save.setShortcut(Qt.CTRL|Qt.Key_S) self.action_Save.triggered.connect(self.OnSave) self.actionParse_Excel.triggered.connect(self.OnPaste) #self.action_Export_Code.triggered.connect(self.OnExportData) self.actionUndo.setShortcut(Qt.CTRL|Qt.Key_Z) self.actionUndo.triggered.connect(self.OnUndo) self.actionRedo.setShortcut(Qt.CTRL|Qt.Key_Y) self.actionRedo.triggered.connect(self.OnRedo) self.SetRootTreeItem('root') #self.currentZip = '' self.timer = QtCore.QTimer() self.timer.timeout.connect(self.DelayStart) self.timer.start(100) def GetAllDatasFromDB(self): cursor = self.db.cursor() cursor.execute('select * from datas order by k asc') values = cursor.fetchall() cursor.close() return values @property def TreeRootItem(self): return self.rootItem def DelayStart(self): self.timer.stop() self.timer = None sf = SelectFolder.SelectFolder(self) if sf.exec_() == QtWidgets.QDialog.Rejected: self.close() sf.destroy() currentPath = sf.GetFolder() if os.path.exists(currentPath) == False: return self.setWindowTitle(self.oldWindowTitle + ' - ' + currentPath) self.db = sqlite3.connect(currentPath) values = self.GetAllDatasFromDB() for k,v,t in values: item = self.AddTreeItem(None,k, t,False) if t == MainWindow.Table: model = GridTableView.TableViewItemModel(2,0) model.setParent(self.tree) if v != 'None': self.LoadTableData(v,model) item.setData(model,Qt.UserRole+2) self.tree.expand(self.rootItem.index())
class DirectoriesDialog(QMainWindow): def __init__(self, app, **kwargs): super().__init__(None, **kwargs) self.app = app self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS self.recentFolders = Recent(self.app, 'recentFolders') self._setupUi() self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView) self.directoriesDelegate = DirectoriesDelegate() self.treeView.setItemDelegate(self.directoriesDelegate) self._setupColumns() self.app.recentResults.addMenu(self.menuLoadRecent) self.app.recentResults.addMenu(self.menuRecentResults) self.recentFolders.addMenu(self.menuRecentFolders) self._updateAddButton() self._updateRemoveButton() self._updateLoadResultsButton() self._setupBindings() def _setupBindings(self): self.scanButton.clicked.connect(self.scanButtonClicked) self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger) self.addFolderButton.clicked.connect(self.actionAddFolder.trigger) self.removeFolderButton.clicked.connect(self.removeFolderButtonClicked) self.treeView.selectionModel().selectionChanged.connect( self.selectionChanged) self.app.recentResults.itemsChanged.connect( self._updateLoadResultsButton) self.recentFolders.itemsChanged.connect(self._updateAddButton) self.recentFolders.mustOpenItem.connect(self.app.model.add_directory) self.directoriesModel.foldersAdded.connect( self.directoriesModelAddedFolders) self.app.willSavePrefs.connect(self.appWillSavePrefs) def _setupActions(self): # (name, shortcut, icon, desc, func) ACTIONS = [ ('actionLoadResults', 'Ctrl+L', '', tr("Load Results..."), self.loadResultsTriggered), ('actionShowResultsWindow', '', '', tr("Results Window"), self.app.showResultsWindow), ('actionAddFolder', '', '', tr("Add Folder..."), self.addFolderTriggered), ] createActions(ACTIONS, self) def _setupMenu(self): self.menubar = QMenuBar(self) self.menubar.setGeometry(QRect(0, 0, 42, 22)) self.menuFile = QMenu(self.menubar) self.menuFile.setTitle(tr("File")) self.menuView = QMenu(self.menubar) self.menuView.setTitle(tr("View")) self.menuHelp = QMenu(self.menubar) self.menuHelp.setTitle(tr("Help")) self.menuLoadRecent = QMenu(self.menuFile) self.menuLoadRecent.setTitle(tr("Load Recent Results")) self.setMenuBar(self.menubar) self.menuFile.addAction(self.actionLoadResults) self.menuFile.addAction(self.menuLoadRecent.menuAction()) self.menuFile.addSeparator() self.menuFile.addAction(self.app.actionQuit) self.menuView.addAction(self.app.actionPreferences) self.menuView.addAction(self.actionShowResultsWindow) self.menuView.addAction(self.app.actionIgnoreList) self.menuHelp.addAction(self.app.actionShowHelp) self.menuHelp.addAction(self.app.actionOpenDebugLog) self.menuHelp.addAction(self.app.actionAbout) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) # Recent folders menu self.menuRecentFolders = QMenu() self.menuRecentFolders.addAction(self.actionAddFolder) self.menuRecentFolders.addSeparator() # Recent results menu self.menuRecentResults = QMenu() self.menuRecentResults.addAction(self.actionLoadResults) self.menuRecentResults.addSeparator() def _setupUi(self): self.setWindowTitle(self.app.NAME) self.resize(420, 338) self.centralwidget = QWidget(self) self.verticalLayout = QVBoxLayout(self.centralwidget) self.promptLabel = QLabel( tr("Select folders to scan and press \"Scan\"."), self.centralwidget) self.verticalLayout.addWidget(self.promptLabel) self.treeView = QTreeView(self.centralwidget) self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection) self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows) self.treeView.setAcceptDrops(True) triggers = QAbstractItemView.DoubleClicked|QAbstractItemView.EditKeyPressed\ |QAbstractItemView.SelectedClicked self.treeView.setEditTriggers(triggers) self.treeView.setDragDropOverwriteMode(True) self.treeView.setDragDropMode(QAbstractItemView.DropOnly) self.treeView.setUniformRowHeights(True) self.verticalLayout.addWidget(self.treeView) self.horizontalLayout = QHBoxLayout() self.removeFolderButton = QPushButton(self.centralwidget) self.removeFolderButton.setIcon(QIcon(QPixmap(":/minus"))) self.removeFolderButton.setShortcut("Del") self.horizontalLayout.addWidget(self.removeFolderButton) self.addFolderButton = QPushButton(self.centralwidget) self.addFolderButton.setIcon(QIcon(QPixmap(":/plus"))) self.horizontalLayout.addWidget(self.addFolderButton) spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.loadResultsButton = QPushButton(self.centralwidget) self.loadResultsButton.setText(tr("Load Results")) self.horizontalLayout.addWidget(self.loadResultsButton) self.scanButton = QPushButton(self.centralwidget) self.scanButton.setText(tr("Scan")) self.scanButton.setDefault(True) self.horizontalLayout.addWidget(self.scanButton) self.verticalLayout.addLayout(self.horizontalLayout) self.setCentralWidget(self.centralwidget) self._setupActions() self._setupMenu() if self.app.prefs.directoriesWindowRect is not None: self.setGeometry(self.app.prefs.directoriesWindowRect) else: moveToScreenCenter(self) def _setupColumns(self): header = self.treeView.header() header.setStretchLastSection(False) header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.Fixed) header.resizeSection(1, 100) def _updateAddButton(self): if self.recentFolders.isEmpty(): self.addFolderButton.setMenu(None) else: self.addFolderButton.setMenu(self.menuRecentFolders) def _updateRemoveButton(self): indexes = self.treeView.selectedIndexes() if not indexes: self.removeFolderButton.setEnabled(False) return self.removeFolderButton.setEnabled(True) def _updateLoadResultsButton(self): if self.app.recentResults.isEmpty(): self.loadResultsButton.setMenu(None) else: self.loadResultsButton.setMenu(self.menuRecentResults) #--- QWidget overrides def closeEvent(self, event): event.accept() if self.app.model.results.is_modified: title = tr("Unsaved results") msg = tr("You have unsaved results, do you really want to quit?") if not self.app.confirm(title, msg): event.ignore() if event.isAccepted(): QApplication.quit() #--- Events def addFolderTriggered(self): title = tr("Select a folder to add to the scanning list") flags = QFileDialog.ShowDirsOnly dirpath = str( QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags)) if not dirpath: return self.lastAddedFolder = dirpath self.app.model.add_directory(dirpath) self.recentFolders.insertItem(dirpath) def appWillSavePrefs(self): self.app.prefs.directoriesWindowRect = self.geometry() def directoriesModelAddedFolders(self, folders): for folder in folders: self.recentFolders.insertItem(folder) def loadResultsTriggered(self): title = tr("Select a results file to load") files = ';;'.join( [tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")]) destination = QFileDialog.getOpenFileName(self, title, '', files) if destination: self.app.model.load_from(destination) self.app.recentResults.insertItem(destination) def removeFolderButtonClicked(self): self.directoriesModel.model.remove_selected() def scanButtonClicked(self): if self.app.model.results.is_modified: title = tr("Start a new scan") msg = tr( "You have unsaved results, do you really want to continue?") if not self.app.confirm(title, msg): return self.app.model.start_scanning() def selectionChanged(self, selected, deselected): self._updateRemoveButton()
class DirectoriesDialog(QMainWindow): def __init__(self, app, **kwargs): super().__init__(None, **kwargs) self.app = app self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS self.recentFolders = Recent(self.app, "recentFolders") self._setupUi() self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView) self.directoriesDelegate = DirectoriesDelegate() self.treeView.setItemDelegate(self.directoriesDelegate) self._setupColumns() self.app.recentResults.addMenu(self.menuLoadRecent) self.app.recentResults.addMenu(self.menuRecentResults) self.recentFolders.addMenu(self.menuRecentFolders) self._updateAddButton() self._updateRemoveButton() self._updateLoadResultsButton() self._setupBindings() def _setupBindings(self): self.scanButton.clicked.connect(self.scanButtonClicked) self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger) self.addFolderButton.clicked.connect(self.actionAddFolder.trigger) self.removeFolderButton.clicked.connect(self.removeFolderButtonClicked) self.treeView.selectionModel().selectionChanged.connect(self.selectionChanged) self.app.recentResults.itemsChanged.connect(self._updateLoadResultsButton) self.recentFolders.itemsChanged.connect(self._updateAddButton) self.recentFolders.mustOpenItem.connect(self.app.model.add_directory) self.directoriesModel.foldersAdded.connect(self.directoriesModelAddedFolders) self.app.willSavePrefs.connect(self.appWillSavePrefs) def _setupActions(self): # (name, shortcut, icon, desc, func) ACTIONS = [ ("actionLoadResults", "Ctrl+L", "", tr("Load Results..."), self.loadResultsTriggered), ("actionShowResultsWindow", "", "", tr("Results Window"), self.app.showResultsWindow), ("actionAddFolder", "", "", tr("Add Folder..."), self.addFolderTriggered), ] createActions(ACTIONS, self) def _setupMenu(self): self.menubar = QMenuBar(self) self.menubar.setGeometry(QRect(0, 0, 42, 22)) self.menuFile = QMenu(self.menubar) self.menuFile.setTitle(tr("File")) self.menuView = QMenu(self.menubar) self.menuView.setTitle(tr("View")) self.menuHelp = QMenu(self.menubar) self.menuHelp.setTitle(tr("Help")) self.menuLoadRecent = QMenu(self.menuFile) self.menuLoadRecent.setTitle(tr("Load Recent Results")) self.setMenuBar(self.menubar) self.menuFile.addAction(self.actionLoadResults) self.menuFile.addAction(self.menuLoadRecent.menuAction()) self.menuFile.addSeparator() self.menuFile.addAction(self.app.actionQuit) self.menuView.addAction(self.app.actionPreferences) self.menuView.addAction(self.actionShowResultsWindow) self.menuView.addAction(self.app.actionIgnoreList) self.menuHelp.addAction(self.app.actionShowHelp) self.menuHelp.addAction(self.app.actionOpenDebugLog) self.menuHelp.addAction(self.app.actionAbout) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) # Recent folders menu self.menuRecentFolders = QMenu() self.menuRecentFolders.addAction(self.actionAddFolder) self.menuRecentFolders.addSeparator() # Recent results menu self.menuRecentResults = QMenu() self.menuRecentResults.addAction(self.actionLoadResults) self.menuRecentResults.addSeparator() def _setupUi(self): self.setWindowTitle(self.app.NAME) self.resize(420, 338) self.centralwidget = QWidget(self) self.verticalLayout = QVBoxLayout(self.centralwidget) self.promptLabel = QLabel(tr('Select folders to scan and press "Scan".'), self.centralwidget) self.verticalLayout.addWidget(self.promptLabel) self.treeView = QTreeView(self.centralwidget) self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection) self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows) self.treeView.setAcceptDrops(True) triggers = ( QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed | QAbstractItemView.SelectedClicked ) self.treeView.setEditTriggers(triggers) self.treeView.setDragDropOverwriteMode(True) self.treeView.setDragDropMode(QAbstractItemView.DropOnly) self.treeView.setUniformRowHeights(True) self.verticalLayout.addWidget(self.treeView) self.horizontalLayout = QHBoxLayout() self.removeFolderButton = QPushButton(self.centralwidget) self.removeFolderButton.setIcon(QIcon(QPixmap(":/minus"))) self.removeFolderButton.setShortcut("Del") self.horizontalLayout.addWidget(self.removeFolderButton) self.addFolderButton = QPushButton(self.centralwidget) self.addFolderButton.setIcon(QIcon(QPixmap(":/plus"))) self.horizontalLayout.addWidget(self.addFolderButton) spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.loadResultsButton = QPushButton(self.centralwidget) self.loadResultsButton.setText(tr("Load Results")) self.horizontalLayout.addWidget(self.loadResultsButton) self.scanButton = QPushButton(self.centralwidget) self.scanButton.setText(tr("Scan")) self.scanButton.setDefault(True) self.horizontalLayout.addWidget(self.scanButton) self.verticalLayout.addLayout(self.horizontalLayout) self.setCentralWidget(self.centralwidget) self._setupActions() self._setupMenu() if self.app.prefs.directoriesWindowRect is not None: self.setGeometry(self.app.prefs.directoriesWindowRect) else: moveToScreenCenter(self) def _setupColumns(self): header = self.treeView.header() header.setStretchLastSection(False) header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.Fixed) header.resizeSection(1, 100) def _updateAddButton(self): if self.recentFolders.isEmpty(): self.addFolderButton.setMenu(None) else: self.addFolderButton.setMenu(self.menuRecentFolders) def _updateRemoveButton(self): indexes = self.treeView.selectedIndexes() if not indexes: self.removeFolderButton.setEnabled(False) return self.removeFolderButton.setEnabled(True) def _updateLoadResultsButton(self): if self.app.recentResults.isEmpty(): self.loadResultsButton.setMenu(None) else: self.loadResultsButton.setMenu(self.menuRecentResults) # --- QWidget overrides def closeEvent(self, event): event.accept() if self.app.model.results.is_modified: title = tr("Unsaved results") msg = tr("You have unsaved results, do you really want to quit?") if not self.app.confirm(title, msg): event.ignore() if event.isAccepted(): QApplication.quit() # --- Events def addFolderTriggered(self): title = tr("Select a folder to add to the scanning list") flags = QFileDialog.ShowDirsOnly dirpath = str(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags)) if not dirpath: return self.lastAddedFolder = dirpath self.app.model.add_directory(dirpath) self.recentFolders.insertItem(dirpath) def appWillSavePrefs(self): self.app.prefs.directoriesWindowRect = self.geometry() def directoriesModelAddedFolders(self, folders): for folder in folders: self.recentFolders.insertItem(folder) def loadResultsTriggered(self): title = tr("Select a results file to load") files = ";;".join([tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")]) destination = QFileDialog.getOpenFileName(self, title, "", files) if destination: self.app.model.load_from(destination) self.app.recentResults.insertItem(destination) def removeFolderButtonClicked(self): self.directoriesModel.model.remove_selected() def scanButtonClicked(self): if self.app.model.results.is_modified: title = tr("Start a new scan") msg = tr("You have unsaved results, do you really want to continue?") if not self.app.confirm(title, msg): return self.app.model.start_scanning() def selectionChanged(self, selected, deselected): self._updateRemoveButton()
class ShellTableEditor(QWidget): def __init__(self, parent=None): super().__init__(parent) self.model = None self.shell_model = ShellTableTreeModel() self.tree_view = QTreeView(self) self.tree_view.setModel(self.shell_model) layout = QVBoxLayout() self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.init_navigation(), 0) layout.addWidget(self.tree_view, 1) layout.addWidget(self.init_docs(), 0) def get_icon(self, name): return self.style().standardIcon(name) def init_navigation(self): self.prev_button = QPushButton(self.get_icon(QStyle.SP_ArrowLeft), "") self.prev_button.setFlat(True) self.next_button = QPushButton(self.get_icon(QStyle.SP_ArrowRight), "") self.next_button.setFlat(True) self.prev_button.clicked.connect(self.handle_prev_button_clicked) self.next_button.clicked.connect(self.handle_next_button_clicked) self.current_index = QSpinBox() self.current_index.setMinimumWidth(60) self.current_index.valueChanged.connect(self.handle_current_index_changed) box = QWidget(self) box_layout = QHBoxLayout(box) box_layout.setContentsMargins(0, 0, 0, 0) box.setLayout(box_layout) box_layout.addWidget(self.prev_button, 0) box_layout.addWidget(self.next_button, 0) box_layout.addWidget(QLabel("Index:"), 0) box_layout.addWidget(self.current_index, 0) box_layout.addStretch(1) return box def init_docs(self): box = QWidget(self) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) box.setLayout(layout) layout.addWidget(QLabel(DOC_CAPACITY), 0, Qt.AlignTop) layout.addWidget(QLabel(DOC_RECOIL), 0, Qt.AlignTop) layout.addWidget(QLabel(DOC_RELOAD), 1, Qt.AlignTop) return box def handle_current_index_changed(self, value): parent_index = QModelIndex() qindex = self.shell_model.index(self.current_index.value(), 0, parent_index) self.tree_view.setRootIndex(qindex) self.tree_view.expandAll() def handle_prev_button_clicked(self): self.current_index.setValue(self.current_index.value() - 1) def handle_next_button_clicked(self): self.current_index.setValue(self.current_index.value() + 1) def set_model(self, model): self.model = model if model is None: self.shell_model.update([]) self.current_index.setMaximum(0) else: self.shell_model.update(self.model.data.entries) self.current_index.setMaximum(len(self.model.data.entries) - 1) self.handle_current_index_changed(0) self.tree_view.header().resizeSection(0, 120)