diff --git a/assignment2/meg/CategorizeObjects.py b/assignment2/meg/CategorizeObjects.py
deleted file mode 100644
index 315cfad..0000000
--- a/assignment2/meg/CategorizeObjects.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-Class sorts the list of objects into categories based on the types of the objects in the list.
-"""
-import os
-import pymel.core as pm
-import pprint
-
-class CategorizeObjects:
- def __init__(self):
- self.objects_list = []
- self.shape_objects_list = []
- self.objects_dictionary = {}
-
- def create_object_containers(self):
- """Calls the methods that create:
- 1. A list of Shape objects.
- 2. A dictionary with object names as values, and their types as keys."""
- self.create_shape_objects_list()
- self.create_objects_dictionary()
-
- def create_shape_objects_list(self):
- """Creates a list of Shape objects from the selection."""
- for each_object in self.objects_list:
- if each_object.listRelatives(shapes=True):
- shape_object = each_object.getShape()
- self.shape_objects_list.append(shape_object)
-
- def create_objects_dictionary(self):
- """Populates the dictionary with objects as values, and their types as keys. """
- for selected_object in self.objects_list:
- if selected_object in self.shape_objects_list: # if it is a shape node, do not add to dictionary
- continue
- elif selected_object.listRelatives(shapes=True): # if it has a shape node as a child,
- shape_object = selected_object.getShape() # get its type by accessing the shape node.
- object_type = pm.objectType(shape_object)
- elif pm.objectType(selected_object) == "transform":
- object_type = "group" # if it contains a transform node but no shape node, could be a group
- else:
- object_type = pm.objectType(selected_object) # else, find it's type
- if object_type not in self.objects_dictionary: # if category is not already a key in the dictionary, add it
- self.objects_dictionary[object_type] = []
- self.objects_dictionary[object_type].append(selected_object) # add the object to the dictionary
-
-
diff --git a/assignment2/meg/CreateSuffixDatabase.py b/assignment2/meg/CreateSuffixDatabase.py
deleted file mode 100644
index cdba824..0000000
--- a/assignment2/meg/CreateSuffixDatabase.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import json
-
-"""Creates a JSON file for storing suffixes for different object types"""
-suffixes = {'mesh': '_geo',
- 'joint': '_jnt',
- 'nurbsCurve': '_crv',
- 'locator': '_loc',
- 'light': "_lgt",
- 'group': "_grp"}
-try:
- with open('Database/SuffixDatabase.json', 'w') as data_file:
- json.dump(suffixes, data_file, indent=2)
-except ValueError as write_error:
- print("Database could not be created.", write_error)
diff --git a/assignment2/meg/Database/SuffixDatabase.json b/assignment2/meg/Database/SuffixDatabase.json
deleted file mode 100644
index 30a9e1b..0000000
--- a/assignment2/meg/Database/SuffixDatabase.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "group": "_grp",
- "nurbsCurve": "_crv",
- "locator": "_loc",
- "light": "_lgt",
- "joint": "_jnt",
- "mesh": "_geo"
-}
\ No newline at end of file
diff --git a/assignment2/meg/NewNameOperations.py b/assignment2/meg/NewNameOperations.py
deleted file mode 100644
index 17e6a2c..0000000
--- a/assignment2/meg/NewNameOperations.py
+++ /dev/null
@@ -1,66 +0,0 @@
-""" This class suggests an object name (its current name + suffix) based on its type. If the object name already
-contains the correct suffix, it returns the name as is. """
-import re
-import os
-import json
-
-def validate_desired_names(desired_names_list):
- """ Checks the new name entry for errors:
- 1. If new name textbox is left empty
- 2. If there is a space : replaces it with underscore
- 3. If there are any special characters (other than underscore)
- 4. If there are any duplicate object names
- In case of error, adds an appropriate error message, and returns True. Else, returns False"""
- error_message = None
- if "" in desired_names_list:
- error_message = "Error: New name invalid."
- return error_message
- for name_index, name in enumerate(desired_names_list):
- if " " in name:
- name = name.replace(" ", "_")
- desired_names_list[name_index] = name
- if re.findall('[^A-Za-z0-9]+', name.replace("_", "")):
- error_message = "Error: Special characters (other than underscore) not allowed."
- return error_message
- if len(desired_names_list) != len(set(desired_names_list)):
- error_message = "Error: Duplicate names."
-
- return error_message
-
-
-def suggest_name(object_name, category_name):
- """ Adds an appropriate suffix to the object name (if there isn't one already), and returns it as the
- suggested name. Eg: The suffix '_geo' is added to a mesh object. If database is not accessible, prints an error and
- returns the name without a suffix
- :param object_name : Name of the object that needs renaming
- :type object_name : String
- :param category_name : Type of the object. Eg: mesh, joint, locator.
- :type category_name : String
- :return object_name : A name suggestion, with a suffix added to the object name(if no error). Suffix is based on
- the object_type.
- :rtype object_name : String """
-
- suffix = "_" + category_name.lower()[:3]
-
- project_path = os.path.dirname(os.path.abspath(__file__))
- database_path = os.path.join(project_path, 'Database\SuffixDatabase.json')
-
- suffix_dict = None
- object_name = str(object_name)
- if not os.path.isfile(database_path) or not os.access(database_path, os.R_OK): # case: data loading failed.
- print("Error: Directory not found or not readable")
- return object_name
- database_file = open(database_path)
-
- try: # case: data loading successful.
- suffix_dict = json.load(database_file)
- if category_name in suffix_dict:
- suffix = suffix_dict[category_name]
- except ValueError as json_error:
- print("Error: ", str(json_error))
- if not suffix_dict:
- print("Error: Suffixes database is empty")
-
- if not object_name.endswith(suffix):
- object_name += suffix
- return object_name
diff --git a/assignment2/meg/ObjectRenamer.py b/assignment2/meg/ObjectRenamer.py
deleted file mode 100644
index 578ca8b..0000000
--- a/assignment2/meg/ObjectRenamer.py
+++ /dev/null
@@ -1,196 +0,0 @@
-""" This is a program to rename selected objects in Maya. The pop-up window will contain:
-a list of the selected objects, sorted by their types,
-and a textbox next to each object, for the user to type the desired new name.
-The textbox is populated with suggestions, based on the object type, and the user can edit it.
-TODO - Future enhancements:
-1. indicate which textbox needs editing, to fix the displayed error.
-2. When user hits Undo once after using the tool, the entire list of names should be reverted at once.
-3. Check more test cases
-4. Add reload button to reload (possibly new) selection while window is still open
-5. Add check-boxes to ask user if modifying suffix for one object should similarly modify suffixes for all
-objects of that category. """
-
-import pymel.core as pm
-from PySide2 import QtWidgets, QtCore
-from maya import OpenMayaUI as omui
-from shiboken2 import wrapInstance
-
-import CategorizeObjects
-import NewNameOperations
-
-class RenamerDialog(QtWidgets.QDialog):
- def __init__(self):
- """ The initialization includes setting up the UI framework for the tool window, and calling the method
- which will do an initial check and start setting up the window."""
- pointer = omui.MQtUtil.mainWindow()
- parent = wrapInstance(long(pointer), QtWidgets.QWidget)
- super(RenamerDialog, self).__init__(parent)
-
- self.setWindowTitle("Object Renamer")
- self.main_layout = QtWidgets.QGridLayout()
- self.main_layout.setAlignment(QtCore.Qt.AlignCenter)
- self.setLayout(self.main_layout)
- self.setGeometry(500, 200, 520, 620)
-
- self.initial_check_and_start()
-
- def initial_check_and_start(self):
- """This method creates a list of all selected dag objects, and checks if selection is empty.
- If it is empty, displays an error message. If not, it goes ahead, and sets up the object lists' display.
- It also creates an object of the CategorizeObjects class, and initializes variables which will be used later."""
-
- self.all_selected_objects = pm.ls(dagObjects=True, selection=True, dependencyNodes=True)
-
- if not self.all_selected_objects:
- no_selection_label = QtWidgets.QLabel("Nothing selected.")
- self.main_layout.addWidget(no_selection_label)
- return
-
- self.categorize_object = CategorizeObjects.CategorizeObjects()
- self.row_index = 0
- self.new_name_dictionary = {}
- self.desired_names_list = []
- self.initialize_ui_elements()
-
- self.populate_window()
-
-
- def initialize_ui_elements(self):
- """Creates all the UI elements for the dialog window, except the dynamically created ones."""
- self.scroll_area_widget = QtWidgets.QScrollArea()
- self.list_widget = QtWidgets.QWidget()
- self.list_grid_layout = QtWidgets.QGridLayout()
-
- self.current_name_label = QtWidgets.QLabel("Current name:")
- self.new_name_label = QtWidgets.QLabel("New Name:")
- self.heading_bar = QtWidgets.QWidget()
- self.heading_bar_layout = QtWidgets.QHBoxLayout()
- self.error_label = QtWidgets.QLabel()
-
- self.rename_button = QtWidgets.QPushButton("Rename and close")
-
- def populate_window(self):
- """ Calls the methods that create and display the UI widgets in the popup window"""
-
- # add heading labels to the top of the window
- self.setup_heading_labels()
-
- # create two lists (one for current object names, one for the desired new names) and add them to the display.
- self.categorize_object.objects_list = self.all_selected_objects
- self.categorize_object.create_object_containers()
- self.setup_scroll_area_widget()
-
- # create a rename button, and add it to the bottom of the display
- self.setup_rename_button()
-
-
- def setup_heading_labels(self):
- """ Encases the labels for 'Current name' and 'New name' list-headings in a QHBox layout, and adds
- them to the main layout. """
- self.heading_bar.setLayout(self.heading_bar_layout)
- self.heading_bar_layout.addWidget(self.current_name_label)
- self.heading_bar_layout.addWidget(self.new_name_label)
- self.main_layout.addWidget(self.heading_bar)
-
- def setup_rename_button(self):
- """ Makes the signal-slot connection for the 'rename and close' button at the bottom.
- i.e., on button press, calls the rename_objects() method. """
- self.rename_button.pressed.connect(self.rename_objects)
- self.main_layout.addWidget(self.rename_button, 2, 0)
-
- def setup_scroll_area_widget(self):
- """ Sets up the UI elements for scrollable area (the lists widget)."""
- self.setup_scroll_area_style()
-
- self.list_widget.setLayout(self.list_grid_layout)
- self.list_grid_layout.setAlignment(QtCore.Qt.AlignTop)
-
- self.populate_list_grid_layout()
-
- self.scroll_area_widget.setWidget(self.list_widget)
- self.main_layout.addWidget(self.scroll_area_widget, 1, 0)
-
- def setup_scroll_area_style(self):
- """ Sets up the style for the scrollable area containing the lists."""
- self.scroll_area_widget.setFocusPolicy(QtCore.Qt.NoFocus)
- self.scroll_area_widget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
- self.scroll_area_widget.setWidgetResizable(True)
-
- def populate_list_grid_layout(self):
- """ The scroll area contains a list of categories(object types), each of which contains a QGridLayout, with all
- the objects of that type, listed under it.
- A new dictionary is created, which will hold: the object names as keys, and name of the textbox that contains
- its new name as the value. This mapping will help with the renaming action, on button press."""
-
- for object_type, objects_list in self.categorize_object.objects_dictionary.items():
- type_name = str(object_type).capitalize() + " objects:"
- category_label = QtWidgets.QLabel(type_name)
- self.list_grid_layout.addWidget(category_label, self.row_index, 0)
- self.row_index += 1
-
- # Create a new category widget (Eg. for Mesh type objects)
- category_wise_grid = QtWidgets.QWidget()
-
- self.category_wise_grid_layout = QtWidgets.QGridLayout() # Dynamic creation
- self.category_wise_grid_layout.setHorizontalSpacing(0)
- self.category_wise_grid_layout.setVerticalSpacing(0)
-
- # Obtain a list of all objects of the category type
- self.populate_each_category(objects_list, object_type)
-
- # Add the category along with its list to the display
- category_wise_grid.setLayout(self.category_wise_grid_layout)
- self.list_grid_layout.addWidget(category_wise_grid, self.row_index, 0)
- self.row_index += 1
- # print(object_type, objects_list)
-
- def populate_each_category(self, all_objects_in_category, category):
- """ For each object in the list, creates two textboxes: one for the current name, and one for the desired new
- name. Adds these textboxes to the layout. The current names have to be non-editable.
- :param all_objects_in_category : List of all objects of the same type. Eg: All locator objects.
- :type all_objects_in_category : List
- :param category : Type of the objects in the list
- :type category : Object type
- """
- object_index = 0
- for each_object in all_objects_in_category:
- suggested_name = NewNameOperations.suggest_name(each_object, category)
- from_textbox = QtWidgets.QLineEdit(str(each_object))
- to_textbox = QtWidgets.QLineEdit(suggested_name)
- from_textbox.setEnabled(False)
-
- self.new_name_dictionary[each_object] = to_textbox
- self.category_wise_grid_layout.addWidget(from_textbox, object_index, 0)
- self.category_wise_grid_layout.addWidget(to_textbox, object_index, 1)
- object_index += 1
-
- def rename_objects(self):
- """ This method goes through the key-value pairs in the new_name_dictionary, and renames the objects in Maya,
- based on the content of the corresponding textboxes.
- The new_name_dictionary contains: the object names as keys,
- and names of the corresponding textboxes (which in turn, contain the desired new names) as the values. """
-
- for every_object, corresponding_textbox in self.new_name_dictionary.items():
- self.desired_names_list.append(corresponding_textbox.text())
- error_message = NewNameOperations.validate_desired_names(self.desired_names_list)
- if error_message:
- self.error_label.setText(self.new_name_object.error_message)
- self.list_grid_layout.addWidget(self.error_label, self.row_index, 0)
- self.row_index += 1
- return
- else:
- for object_index, every_object in enumerate(self.new_name_dictionary.keys()):
- pm.rename(every_object, self.desired_names_list[object_index])
- self.close()
-
-
-INSTANCE = None
-
-
-def show_gui():
- """ Singleton to create the gui if it doesn't exist, or show if it does """
- global INSTANCE
- if not INSTANCE:
- INSTANCE = RenamerDialog()
- INSTANCE.show()
- return INSTANCE
diff --git a/assignment2/meg/ReadMe.md b/assignment2/meg/ReadMe.md
deleted file mode 100644
index fe41991..0000000
--- a/assignment2/meg/ReadMe.md
+++ /dev/null
@@ -1,10 +0,0 @@
-This is a tool to rename the selected objects in Maya.
-The tool interface, which is a pop-up window, will contain two columns: one with a list of the selected objects, sorted by object type; and the other, with a list of textboxes where the user can type the desired new name.
-The second column is populated with suggestions, based on the object type, and the user can edit it.
-
-The logic is pretty straight forward. The only point to note, is that objects such as polygons and locators contain two nodes: a transform node and a shape node. The shape node is listed as a child of the transform node. In order to find out the type of the objects, we need to access the shape node. The renaming, however, must be done to its transform node.
-
-
-Here's a screenshot of the tool:
-
-
\ No newline at end of file
diff --git a/assignment2/meg/RenamerScreenshot.jpg b/assignment2/meg/RenamerScreenshot.jpg
deleted file mode 100644
index 3a96928..0000000
Binary files a/assignment2/meg/RenamerScreenshot.jpg and /dev/null differ
diff --git a/assignment2/meg/__init__.py b/assignment2/meg/__init__.py
deleted file mode 100644
index 81786b4..0000000
--- a/assignment2/meg/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-def __init__(self):
- print("This is the init file. Maya wont recognize this folder without it.")
- pass
\ No newline at end of file
diff --git a/browserApp/.idea/.gitignore b/browserApp/.idea/.gitignore
new file mode 100644
index 0000000..e7e9d11
--- /dev/null
+++ b/browserApp/.idea/.gitignore
@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml
diff --git a/browserApp/.idea/browserApp.iml b/browserApp/.idea/browserApp.iml
new file mode 100644
index 0000000..94483d8
--- /dev/null
+++ b/browserApp/.idea/browserApp.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/browserApp/.idea/inspectionProfiles/profiles_settings.xml b/browserApp/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/browserApp/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/browserApp/.idea/misc.xml b/browserApp/.idea/misc.xml
new file mode 100644
index 0000000..e264df9
--- /dev/null
+++ b/browserApp/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/browserApp/.idea/modules.xml b/browserApp/.idea/modules.xml
new file mode 100644
index 0000000..cd0cd42
--- /dev/null
+++ b/browserApp/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/browserApp/.idea/vcs.xml b/browserApp/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/browserApp/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/browserApp/app.py b/browserApp/app.py
index 412c73c..ff6855b 100644
--- a/browserApp/app.py
+++ b/browserApp/app.py
@@ -9,8 +9,10 @@
import sys
-from browserApp import info_view, tree_browser, collection_view
-from browserApp.model import file as model_file
+# from browserApp import info_view, tree_browser, collection_view
+# from browserApp.model import file as model_file
+import info_view, tree_browser, collection_view
+from model import file as model_file
class App(QtWidgets.QMainWindow):
@@ -38,8 +40,8 @@ def __init__(self, top_item, parent=None):
layout.addWidget(self.collection)
# Info view (on right)
- self.info = info_view.InfoView()
- layout.addWidget(self.info)
+ # self.info = info_view.InfoView()
+ # layout.addWidget(self.info)
self.main_widget.setLayout(layout)
@@ -49,15 +51,12 @@ def __init__(self, top_item, parent=None):
self.show()
-
def itemSelected(self, model_info):
""" User as clicked on an item in the tree, pass item's data to the info view """
- self.info.populate(model_info)
+ # self.info.populate(model_info)
self.collection.populate(model_info)
-
-
def show_app(top_item):
app = QtWidgets.QApplication(sys.argv)
win = App(top_item)
@@ -65,6 +64,7 @@ def show_app(top_item):
if __name__ == "__main__":
- top_item = model_file.FileItem("/Volumes/T7/GhostKid")
+ # top_item = model_file.FileItem("/Volumes/T7/GhostKid")
+ top_item = model_file.FileItem("D:\Programming\AlsSchool_Backup\class_3")
- show_app(top_item)
\ No newline at end of file
+ show_app(top_item)
diff --git a/browserApp/collection_view.py b/browserApp/collection_view.py
index e84e22b..a5f8f53 100644
--- a/browserApp/collection_view.py
+++ b/browserApp/collection_view.py
@@ -5,72 +5,232 @@
except:
from PyQt5 import QtWidgets, QtCore
+from os import path, system, remove, listdir
+from os.path import isfile, join
+import model
+# from os import listdir, system, path, remove, rename
+from functools import partial
+import info_view
+
class CollectionView(QtWidgets.QWidget):
def __init__(self, parent=None):
+ """Initializes the Qt Widgets and class variables."""
super(CollectionView, self).__init__(parent)
layout = QtWidgets.QHBoxLayout()
-
- #self.label = QtWidgets.QLabel()
- self.labels = []
-
- layout.addSpacing(1)
- #layout.addWidget(self.label)
- layout.addSpacing(1)
-
self.setLayout(layout)
- def populate(self, items):
- #self.label.clear()
- for label in self.labels:
- label.clear()
- self.layout().removeWidget(label)
- self.labels.clear()
+ self.icons_table_widget = QtWidgets.QTableWidget()
+ self.icons_table_widget.cellClicked.connect(self.display_file_info)
+ self.icons_table_widget.cellDoubleClicked.connect(self.open_item)
+
+ self.icon_image_path = "/images/folder_icon.png"
+ self.NUMBER_OF_GRID_COLUMNS = 1
+ self.data = None
+ self.item_paths = []
+
+ self.info_obj = info_view.InfoView()
+
+ layout.addWidget(self.icons_table_widget)
+ layout.addWidget(self.info_obj)
+
+ def populate(self, data):
+ """ Populates the Collection View area of the app.
+ :param data: object that contains the full path of the folder(or file), the list of files it contains, and some
+ methods to help process the data.
+ :type data: object of file.FileItem """
+ self.data = data
+ meta = data.get_info()
+ if 'full_path' not in meta:
+ print("Could not find file path in dictionary")
+ return
+ item_path = meta['full_path']
+
+ # Clear Icons View:
+ self.icons_table_widget.clearContents()
+ self.item_paths = []
- child_count = items.children()
- if child_count == 0:
+ if 'type' not in meta:
+ print("Could not find item type (file or folder) in dictionary.")
return
- # file_size = 0
- #
- # for i in range(child_count):
- # subitem = items.get_child(i)
- # meta = subitem.get_info()
- #
- # if "file_size" in meta:
- # file_size += meta["file_size"]
- #
- # self.label.setText("Total Size: %d" % file_size)
-
- keys = set()
- integer_values = {}
-
- # Get keys
- for i in range(child_count):
- subitem = items.get_child(i)
- meta = subitem.get_info()
- keys.update(meta.keys())
-
- # Get cumulative key values
- for i in range(child_count):
- subitem = items.get_child(i)
- meta = subitem.get_info()
-
- for key in [x for x in keys if x in meta]:
- value = meta[key]
- if isinstance(value, int):
- if key not in integer_values:
- integer_values[key] = 0
- integer_values[key] += value
-
- for key, value in integer_values.items():
- label = QtWidgets.QLabel("Total {}: {}".format(key, value))
- self.labels.append(label)
- self.layout().addWidget(label)
-
- print(integer_values)
+ # File:
+ if meta['type'] == 'File': # If we remove files from the collection view, we can delete this check.
+ return
+ # Directory:
+ list_of_files = []
+ list_of_folders = []
+ list_of_files_and_folders = listdir(item_path)
+ for item in list_of_files_and_folders:
+ if path.isfile(path.join(item_path, item)): # Request addition of 'file_name' to data model.
+ list_of_files.append(item)
+ else:
+ list_of_folders.append(item)
+
+ self.icons_table_widget.setRowCount(0)
+ self.icons_table_widget.setColumnCount(0)
+ self.icons_table_widget.insertColumn(0)
+ self.icons_table_widget.horizontalHeader().setVisible(False)
+
+ for item in list_of_files_and_folders:
+ # insert a row
+ row_pos = self.icons_table_widget.rowCount()
+ self.icons_table_widget.insertRow(row_pos)
+
+ self.add_item_to_iconview(item, item_path)
+ self.item_paths.append(item_path + "\\" + item)
+
+ def display_file_info(self, clicked_row): # Todo: Does not work if there are any spaces in the file name
+ """If user single clicks on a file, creates an object of InfoView which displays details about the file.
+ contents of the folder that is clicked on.
+ :param clicked_row: row index of the cell that the user clicks on
+ :type clicked_row: int"""
+ file_path = self.item_paths[clicked_row]
+
+ if isfile(file_path):
+ self.info_obj.display_info(file_path)
+ else:
+ print("Not handled folders yet. App is still under construction.")
+
+ def show_rightclick_menu(self, button, point):
+ """ Displays a menu on right mouse button click.
+ :param button: button user clicks on.
+ :type button: QPushButton
+ :param point: ??
+ :type point: QtCore.QPoint
+ """
+
+ # create context menu
+ pop_menu = QtWidgets.QMenu(self)
+
+ delete_action = QtWidgets.QAction('Delete', self)
+ pop_menu.addAction(delete_action)
+ delete_action.triggered.connect(partial(self.delete_item, button))
+
+ open_action = QtWidgets.QAction('Open', self)
+ pop_menu.addAction(open_action)
+ open_action.triggered.connect(partial(self.open_item, button))
+
+ # rename_action = QtWidgets.QAction('Rename', self)
+ # pop_menu.addAction(rename_action)
+ # rename_action.triggered.connect(partial(self.rename_item, button))
+
+ # show context menu
+ pop_menu.exec_(button.mapToGlobal(point))
+
+ def delete_item(self, button):
+ """ Deletes the selected file.
+ :param button: button user clicks on.
+ :type button: QPushButton
+ """
+ if path.exists(self.buttons_dictionary[button]):
+ remove(self.buttons_dictionary[button])
+ print("Deleted:", self.buttons_dictionary[button])
+
+ # refresh view
+ self.refresh_details_view()
+
+ def refresh_details_view(self):
+ """ Refreshes the view. """
+ pass # Todo
+
+ def open_item(self, clicked_row):
+ """ If user double-clicks on a file, opens it.
+ :param clicked_row: row index of the cell in the table widget that the user clicks on
+ :type clicked_row: int
+ """
+ file_path = self.item_paths[clicked_row]
+ # file_path = self.buttons_dictionary[button]
+ if path.exists(file_path):
+ print("Opening", file_path + "...")
+ if " " in file_path:
+ print("Cannot process file names with spaces, yet.")
+ return
+ if path.isfile(file_path):
+ system("start " + file_path)
+ else:
+ # if it's a folder, clear both pages, and populate with data inside that folder
+ # clear_grid_layout(self.icons_layout)
+ self.icons_table_widget.clearContents()
+ file_item_object = model.file.FileItem(
+ file_path) # to generate data, create object of model.file FileItem
+ self.populate(file_item_object)
+
+ def add_item_to_iconview(self, file, file_path):
+ """Creates a TableCellWidget widget. The widget's layout contains an icon and a text(file name).
+ Adds this widget to the next row in icons_table_widget.
+ :param file: file name including extension.
+ :type file: str
+ :param file_path: full path to the file
+ :type file_path: str """
+
+ file_name = file.split('.')[0] # removes the extension.
+
+ if "." not in file_path: # Todo: use a different method. What if the file name contains a dot?
+ full_path = join(file_path, file)
+ else:
+ full_path = file_path
+
+ cell_widget = TableCellWidget()
+ cell_widget.create_widget(file_name, full_path)
+
+ self.icons_table_widget.setCellWidget(self.icons_table_widget.rowCount()-1, 0, cell_widget)
+ self.set_table_style()
+
+ def set_table_style(self):
+ """Sets the style of the icons_table_widget QtTableWidget"""
+ self.icons_table_widget.setColumnWidth(0, 200)
+ self.icons_table_widget.verticalHeader().setVisible(False)
+ self.icons_table_widget.setShowGrid(False)
+
+# def set_button_style(button):
+# """ Sets the button style and dimensions. No background or border by default. Turns blue with a border on hover.
+# :param button: the buttons with the file name and icon on them.
+# :type button: QWidgets.QPushButton """
+# button.setFixedSize(250, 25)
+# button.setStyleSheet(""" QPushButton {text-align:left; background-color: none; border: none; }
+# QPushButton:hover { background-color: #CBE1F5 }
+# QPushButton:pressed { border-width: 5px; background-color: #B7D9F9 } """)
+
+
+class TableCellWidget(QtWidgets.QWidget):
+ """Class to create a layout with a label and an icon. Useful for creating file buttons in a QTableWidget
+ for a file browser."""
+ def __init__(self, parent=None):
+ super(TableCellWidget, self).__init__(parent)
+
+ self.layout = QtWidgets.QHBoxLayout()
+
+ # adjust spacings between the icon and the text
+ self.layout.setContentsMargins(0, 0, 0, 0)
+
+ self.icon_label = QtWidgets.QLabel()
+ self.text_label = QtWidgets.QLabel()
+ self.layout.setAlignment(QtCore.Qt.AlignLeft)
+ self.setLayout(self.layout)
+
+ def create_widget(self, file_name, file_path):
+ """Creates the TableCellWidget widget for the given file name and path.
+ param file_name: name of the file to be displayed
+ type file_name: str
+ param file_path: path to the file
+ type file_path: str"""
+ self.create_icon(file_path)
+ self.layout.addWidget(self.icon_label)
+ self.text_label.setText(file_name)
+ self.layout.addWidget(self.text_label)
+
+ def create_icon(self, file_path):
+ """ Creates icon based on the default icon for the OS.
+ :param file_path: full path to the file whose icon is to be added to the button
+ :type file_path: str"""
+ file_info = QtCore.QFileInfo(file_path)
+ icon_provider = QtWidgets.QFileIconProvider()
+ icon = icon_provider.icon(file_info)
+ pic = icon.pixmap(10, 10)
+ self.icon_label.setPixmap(pic)
diff --git a/browserApp/file_app.py b/browserApp/file_app.py
index 0fdc6d6..3e19089 100644
--- a/browserApp/file_app.py
+++ b/browserApp/file_app.py
@@ -10,4 +10,4 @@ def start_app():
if __name__ == "__main__":
- start_app()
\ No newline at end of file
+ start_app()
diff --git a/browserApp/info_view.py b/browserApp/info_view.py
index e7138dd..0f78e41 100644
--- a/browserApp/info_view.py
+++ b/browserApp/info_view.py
@@ -1,41 +1,165 @@
-""" This will show detailed information about an item """
+""" This will show detailed information about an item.
+Assumptions made: file and folder names doe not contain spaces or dots. Only one dot, before extension."""
+
+"""Todo:
+1. Files should open only on double click.
+2. Allow selection of multiple items.
+3. Layout width is fixed to make it uniform. Make it scalable.
+4. Allow folders and files with spaces in the name
+5. Allow folders and files with dots in the name? Not sure. Maybe.
+6. Add a separator in-between the header cells in the details table, so that the user can widen the columns if needed.
+7. Remove bold font for selected column in header
+8. Create buttons only for the selected View type, and not for all views.
+9. Request more data from model. Refer to inline comments.
+10. Add LMB click options.
+"""
+
+from os import path
+import model.file
try:
- from PySide2 import QtWidgets, QtCore
+ from PySide2 import QtWidgets, QtCore, QtGui
+
create_signal = QtCore.Signal
except:
- from PyQt5 import QtWidgets, QtCore
+ from PyQt5 import QtWidgets, QtCore, QtGui
+
create_signal = QtCore.pyqtSignal
class InfoView(QtWidgets.QWidget):
def __init__(self, parent=None):
+ """Initializes all necessary Qt widgets and variables."""
super(InfoView, self).__init__(parent)
- layout = QtWidgets.QHBoxLayout()
+ layout = QtWidgets.QVBoxLayout()
- self.table = QtWidgets.QTreeWidget()
- self.table.setColumnCount(2)
+ self.details_widget = QtWidgets.QTableWidget()
layout.addSpacing(1)
- layout.addWidget(self.table)
+ layout.addWidget(self.details_widget)
layout.addSpacing(1)
+ self.file_path = ""
+ self.data = None
+
+ self.details_header_list = ["Name", "Created", "Modified", "Type", "Size"] # Is this a string constant list??
+ self.details_row_index = 0
+ self.details_column_index = 0
self.setLayout(layout)
+ self.setMinimumWidth(200)
+
+ def display_info(self, file_path):
+ """ Populates Info View area of the app with details such as file name, extension, size, date created, etc.
+ :param file_path: full path of the file
+ :type file_path: str """
+ clear_table_widget(self.details_widget)
+
+ self.file_path = file_path
+ self.populate_info_table(file_path)
+ self.reset_row_column_counts()
+
+ def reset_row_column_counts(self):
+ """ Resets the row and column indices to zero. """
+ self.details_row_index = 1
+ self.details_column_index = 1
+ self.details_widget.rowCount = 1
+
+ def populate_info_table(self, file_path):
+ """ Populates the details table widget with data.
+ If it is the first time it is being called, sets up the headers.
+ :param file_path: path to the selected file
+ :type file_path: str
+ """
+ if self.details_row_index == 0:
+ create_table_widget_header(self.details_widget, self.details_header_list)
+
+ self.details_column_index += 1
+
+ # row : filename
+ file_name = path.split(file_path)[1]
+ self.details_widget.setItem(0, 1, QtWidgets.QTableWidgetItem(file_name))
+ file_item_object = model.file.FileItem(file_path) # to access details, create object of model.file FileItem
+ item_data = file_item_object.get_info() # get the data dictionary and store in item_data
+
+ # row : date and time created
+ if 'created' in item_data:
+ date_created = item_data['created']
+ self.details_widget.setItem(1, 1, QtWidgets.QTableWidgetItem(date_created))
+
+ if 'modified' in item_data:
+ date_modified = item_data['modified']
+ self.details_widget.setItem(2, 1, QtWidgets.QTableWidgetItem(date_modified))
+
+ # row : file type
+ file_type = None
+ if 'file_type' in item_data:
+ file_type = item_data['file_type']
+ elif file_name:
+ file_type = file_name.split('.')[-1].upper()
+ if file_type:
+ self.details_widget.setItem(3, 1, QtWidgets.QTableWidgetItem(file_type))
+
+ # row : size
+ if 'file_size' in item_data:
+ file_size = item_data['file_size'] # request this for folders from data model
+ if file_size.isdigit():
+ file_size = float(file_size)
+ file_size = convert_filesize_to_str(file_size)
+
+ self.details_widget.setItem(4, 1, QtWidgets.QTableWidgetItem(file_size))
+
+ self.details_table_style()
+
+ def details_table_style(self):
+ """Sets the style for the details table widget."""
+
+ self.details_widget.setShowGrid(False)
+ self.details_widget.horizontalHeader().setVisible(False)
+ self.details_widget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) # Todo: Make filenames editable
+ self.details_widget.resizeColumnsToContents()
+ self.details_widget.setStyleSheet("QTableWidget {background-color: transparent; border: none}"
+ "QHeaderView::section {background-color: transparent;"
+ "border-right:1px solid gray;}"
+ "QHeaderView {background-color: transparent;"
+ "border-right: 1px solid gray;}"
+ "QTableCornerButton::section {background-color: transparent;}")
+ self.details_widget.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignLeft) # left align header text
- def populate(self, data):
- print("Do something useful with: " + str(data))
- # Clear the table
- self.table.clear()
+def create_table_widget_header(widget, header_items):
+ """ Creates the header for a table widget.
+ :param widget: the widget for which the header needs to be added.
+ :type widget: QTableWidget
+ :param header_items: List of headings for the table's columns
+ :rtype header_items: list of strings
+ """
+ widget.setColumnCount(2) # ?? Re-think this.
+ widget.setRowCount(len(header_items))
+ widget.setVerticalHeaderLabels(header_items)
- # Update the table with name : value from the get_info() dict
- meta = data.get_info()
- for k, v in meta.items():
- item = QtWidgets.QTreeWidgetItem([str(k),str(v)])
- self.table.addTopLevelItem(item)
+def clear_table_widget(table):
+ """ Clears the contents of the table. Keeps the headers intact.
+ :param table: table widget that needs to be cleared.
+ :type table: QtWidgets.QTableWidget
+ """
+ table.clearContents() # Might remove this method if not other operations are needed here.
+def convert_filesize_to_str(size_long):
+ """ Converts the file size from Bytes to KB, MB or GB.
+ :param size_long: file size in long float
+ :type size_long: long integer
+ :return string value: depending on what range the input fits in, returns the value in Kilo, Mega or Giga Bytes.
+ :rtype string value: str """
+ if size_long < 1000:
+ return str(size_long) + " B"
+ elif size_long < 10 ** 6:
+ return str(size_long / (10 ** 3)) + " KB"
+ elif size_long < 10 ** 9:
+ return str(size_long / (10 ** 6)) + " MB"
+ else:
+ return str(size_long / (10 ** 9)) + " GB"
diff --git a/browserApp/model/file.py b/browserApp/model/file.py
index 942a99d..1ad0486 100644
--- a/browserApp/model/file.py
+++ b/browserApp/model/file.py
@@ -1,4 +1,5 @@
-from browserApp.model.base import BaseItem
+# from browserApp.model.base import BaseItem
+from model.base import BaseItem
import os
from datetime import datetime
@@ -57,7 +58,9 @@ def get_info(self):
if os.path.isdir(self.full_path):
+ # stat = os.stat(self.full_path)
data['type'] = "Dir"
+ # data['created'] = str(stat.st_ctime)
return data
diff --git a/browserApp/resources/MediaFiles_Metadata.docx b/browserApp/resources/MediaFiles_Metadata.docx
new file mode 100644
index 0000000..4de5745
Binary files /dev/null and b/browserApp/resources/MediaFiles_Metadata.docx differ
diff --git a/browserApp/tree_browser.py b/browserApp/tree_browser.py
index 5bffa51..55990fa 100644
--- a/browserApp/tree_browser.py
+++ b/browserApp/tree_browser.py
@@ -7,8 +7,8 @@
from PyQt5 import QtWidgets, QtCore, QtGui
create_signal = QtCore.pyqtSignal
-from browserApp.model import base
-
+# from browserApp.model import base
+from model import base
class TreeBrowser(QtWidgets.QTreeWidget):