#!/usr/bin/python ''' ========================================================================================================= T&T Software and Systems Project: Renumbering tool Module: Main module Description: Tool to renumber ohoto and video replace VB one Tiziano Trabattoni | 2016/12/10 | first version Tiziano Trabattoni | 2022/07/31 | Porting su MacOS, PyQt5, python3 Tiziano Trabattoni | 2023/05/22 | Porting su MacOS per pyqt6 Tiziano Trabattoni | 2023/08/14 | Sorting by Creation date with exiftool Tiziano Trabattoni | 2024/08/19 | Changed movie tags to include filemodification date for .mp4 from Android Tiziano Trabattoni | 2024/12/28 | Introduced possibility to ignore metadata and sort only by filename for Undetermined Metadata (old slides) Tiziano Trabattoni | 2025/01/08 | Added steps for numbering ####### Developed under poetry (with pyqt6 M1 ARM64 code available) ~/Develop/Personal/PICT_poetry » poetry remove pyqt5 ~/Develop/Personal/PICT_poetry » poetry add pyqt6 ~/Develop/Personal/PICT_poetry » pip freeze ttrabatt@TTRABATT-M-3QXG PyQt6==6.5.0 PyQt6-Qt6==6.5.0 PyQt6-sip==13.5.1 (pict-numtool-py3.9) ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ~/Develop/Personal/PICT_poetry » poetry show ttrabatt@TTRABATT-M-3QXG pyqt6 6.5.0 Python bindings for the Qt cross platform application toolkit pyqt6-qt6 6.5.0 The subset of a Qt installation needed by PyQt6. pyqt6-sip 13.5.1 The sip module support for PyQt6 (pict-numtool-py3.9) ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ~/Develop/Personal/PICT_poetry » ========================================================================================================= ''' import sys import os import shutil import time import logging import threading import subprocess import tempfile import json from datetime import datetime from collections import OrderedDict ''' PyQT6 imports''' from PICT_numtool_ui import Ui_MainWindow # from PyQt import QtGui # from PyQt.QtWidgets import QTreeView, QLabel, QTreeWidgetItem, QMainWindow, QApplication, QFileDialog, QAbstractItemView # from PyQt.QtGui import QPixmap from PICT_numtool_ui import Ui_MainWindow from PyQt6 import QtGui from PyQt6.QtWidgets import QTreeView, QLabel, QTreeWidgetItem, QMainWindow, QApplication, QFileDialog, QAbstractItemView from PyQt6.QtGui import QPixmap #from PyQt5.QtCore import * #from PyQt6 import QtCore global LOGGER ''' Module version ''' mod_ver = "v2.3" mod_dver = "2025:01:08" ''' Logger Variables ''' LOG_FORMAT = '%(asctime)s|%(levelname)-7s|%(funcName)-15s|%(lineno)-5d: %(message)-50s' LOG_TIME_FORMAT = '%m-%d %H:%M:%S' CONSOLE_DEBUG = logging.INFO FILE_DEBUG = logging.INFO PHOTO_filter = 'Images (*.jpg *.jpeg *.png *.tiff *.heic, *.tif, *.HEIC)' MOVIE_filter = 'Movies (*.mov *.mp4)' # file format and extension variables EXIFTOOL = "exiftool" EXIF_PHOTO_EXT = (".jpg", ".JPG", ".jpeg", ".JPEG", ".png", ".PNG", ".heic", ".HEIC", ".tiff", ".TIFF", ".tif", ".TIF") EXIF_MOVIE_EXT = (".mov", ".MOV", ".mp4", ".MP4",) EXIF_PHOTO_TAGS = ("-datetimeoriginal", "-filecreatedate", "-filemodifydate",) EXIF_MOVIE_TAGS = ("-mediacreatedate","-filemodifydate",) FILE_TREE_CNTL = [('Filename.', 300, QTreeView), ('FullNAme', 800, QtGui.QTextItem), ('--', 20, QLabel)] YEARS_RANGE = [1970, 2040] COL_WHITE = "QPushButton { color: white;}" COL_RED = "QPushButton { color: red;}" COL_GREEN = "QPushButton { color: green;}" COL_YELLOW = "QPushButton { color: yellow;}" COL_BLUE = "QPushButton { color: blue;}" COL_BLACK = "QPushButton { color: black;}" BCOL_WHITE = "background-color: white;" BCOL_RED = "background-color: red;" BCOL_GREEN = "background-color: green;" BCOL_YELLOW = "background-color: yellow;" BCOL_CYAN = "background-color: cyan;" BCOL_BLACK = "background-color: black;" ''' Default instances ''' DEF_INSTANCE = 'select' NON_FILE_CHARACTERS = '\\|/*$?:;<>+\"\'' ''' Ordering dictionary ''' WorkData = {} WorkDir = '' TmpDir = '' class PICTitem(QTreeWidgetItem): ''' Custom QTreeWidgetItem with Widgets ''' _basename = "" _name = "" @property def basename(self): return self._basename @property def name(self): return self._name def __init__(self, parent, basename, name): ''' parent (QTreeWidget) : Item's QTreeWidget parent. value (str) : Item's name. just an example. ''' ## Init super class ( QtGui.QTreeWidgetItem ) super(PICTitem, self).__init__(parent) ''' ''' self.setText(0, basename) #self.setTextAlignment(0, QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.setText(1, name) #self.setTextAlignment(1, QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self._name=name self._basename=basename def __repr__(self) -> str: return str(self) def __str__(self): return f" Filename:{self.basename}" ''' ======================== MAIN WINDOW CLASS with all methods ================================ ''' class main(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) ''' create Progressbar element ''' self.pb = PB(self.ui.pb_Run,mi = 0, ma =100) ''' set default values ''' self.day = 0 self.month = 0 self.year = 1900 self.name = '' self.fullName = '' self.ineditname = False self.filecriteria = PHOTO_filter self.TmpDir = '' self.WorkDir = '' self.WorkData = {} self.toRename = [] ''' set the tree_OUT widget''' self.ui.tree_OUT.setColumnCount(len(FILE_TREE_CNTL)) self.ui.tree_OUT.setHeaderLabels(self.set_qtree_headers(FILE_TREE_CNTL)) self.ui.tree_OUT.setUniformRowHeights(True) #self.ui.tree_OUT.setSelectionMode(QabsW.ExtendedSelection) ''' set all Methods ''' self.ui.btn_Renumber.setEnabled(False) self.ui.btn_Renumber.clicked.connect(self.click_Renumber) self.ui.btn_Open.clicked.connect(self.click_Open) self.ui.btn_Exit.clicked.connect(self.click_Exit) #self.ui.txt_Name.editingFinished.connect(self.edited_Name) self.ui.rbtn_Photo.setChecked(True) ''' set all combo ''' for i in range(1,32): self.ui.cmb_Day.addItem(str(i)) for i in range(1,13): self.ui.cmb_Month.addItem(str(i)) for i in range(YEARS_RANGE[0],YEARS_RANGE[1]+1): self.ui.cmb_Year.addItem(str(i)) for i in range(1,11): self.ui.cmb_Step.addItem(str(i)) ''' set date and time ''' # print datetime.now().strftime('%d/%m/%Y') self.day = int(datetime.now().strftime('%d')) self.month = int(datetime.now().strftime('%m')) self.year = int(datetime.now().strftime('%Y')) self.ui.cmb_Day.setCurrentIndex(self.day - 1) self.ui.cmb_Month.setCurrentIndex(self.month - 1) self.ui.cmb_Year.setCurrentIndex(self.year - YEARS_RANGE[0]) self.ui.date_Day.setDate(datetime.now()) self.ui.txt_Name.textChanged.connect(self.changed_Name) ######## These are OLD PyQT4 signal sintax ########### #''' set Object signals ''' #''' visualization selection via Radio Button ''' #self.ui.rbtn_Movies.connect(self.ui.rbtn_Movies, QtCore.SIGNAL("clicked()"),self.selected_rbtn_Movies) #self.ui.rbtn_Photo.connect(self.ui.rbtn_Photo, QtCore.SIGNAL("clicked()"), self.selected_rbtn_Photos) #self.ui.date_Day.connect(self.ui.date_Day, QtCore.SIGNAL("dateChanged(QDate)"), self.change_date) # #''' Signals ''' #self.ui.cmb_Day.connect(self.ui.cmb_Day, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.choose_Day) #self.ui.cmb_Month.connect(self.ui.cmb_Month, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.choose_Month) #self.ui.cmb_Year.connect(self.ui.cmb_Year, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.choose_Year) ## self.ui.txt_Name.connect(self.ui.txt_Name, QtCore.SIGNAL("insertPlainText (const Qstring&"), self.changed_Name) ''' set Object signals ''' ''' visualization selection via Radio Button ''' self.ui.rbtn_Movies.clicked.connect(self.selected_rbtn_Movies) self.ui.rbtn_Photo.clicked.connect(self.selected_rbtn_Photos) self.ui.date_Day.dateChanged.connect(self.change_date) self.ui.tree_OUT.itemSelectionChanged.connect(self.on_selectionChanged) ''' Signals ''' self.ui.cmb_Day.currentIndexChanged.connect(self.choose_Day) self.ui.cmb_Month.currentIndexChanged.connect(self.choose_Month) self.ui.cmb_Year.currentIndexChanged.connect(self.choose_Year) ''' other setting ''' self.ui.pb_Run.setMinimum(0) self.ui.pb_Run.setMaximum(100) self.ui.pb_Run.setVisible(False) self.ui.chk_IgnMeta.setChecked(False) def click_Exit(self): sys.exit(0) return def QaddFILEChild(self, parent, basename, name): item = PICTitem(parent, basename, name) return item def on_selectionChanged(self): selected = [ s.basename for s in self.ui.tree_OUT.selectedItems() ] for f in self.WorkData: if f in selected: self.WorkData[f]['rename'] = True else: self.WorkData[f]['rename'] = False return def click_Open(self): ''' open source directory ''' #files = QFileDialog.getOpenFileNames(self, "Open Directory", "" , self.filecriteria, None, QFileDialog.DontUseNativeDialog) files = QFileDialog.getOpenFileNames(self, "Open Directory", "" , self.filecriteria, None) nf = len(files) # SHIT now QfileDialog return a Tuple instead of list # Has to be addressed as [0] # because files[1] contains the Type: , - Value >Images (*.jpg *.jpeg *.png *.tiff) if len(files[0]) > 0: ''' clear and set the tree_OUT widget''' self.ui.tree_OUT.clear() self.ui.tree_OUT.setColumnCount(len(FILE_TREE_CNTL)) self.ui.tree_OUT.setHeaderLabels(self.set_qtree_headers(FILE_TREE_CNTL)) self.ui.tree_OUT.setUniformRowHeights(True) self.pb.start(steps=nf) ''' initialized work list dictionary ''' self.WorkData = {} # LOGGER.debug("Type of files[] >%s<" % (str(type(files)))) # self.WorkDir = os.path.dirname(str(files[0])) # SHIT now QfileDialog return a Tuple instead of list # Has to be addressed as [0] # because files[1] contains the Type: , - Value >Images (*.jpg *.jpeg *.png *.tiff) fileuno = files[0][0] #LOGGER.debug("fileuno >%s<" % fileuno) self.WorkDir = os.path.dirname(fileuno) LOGGER.debug(f"Workdir >{self.WorkDir}<") ''' tempory dir must be on same disk otherwise is too slow''' basedir = os.path.dirname(self.WorkDir) self.TmpDir = basedir + '/PICTtool-tmp' if not os.path.exists(self.TmpDir): os.mkdir(self.TmpDir) count = 1 #for fname in files[0]: # LOGGER.debug("Type: %s, - Value >%s<" % (type(fname), fname)) ''' for all selected files ''' for fname in files[0]: self.pb.incr() self.repaint() ''' add an item to Qtree including resizing of pictures ''' # self.add_Qtree_item(fname) basename = os.path.basename(str(fname)) LOGGER.info(f"Reading Metadata from: >{basename}< ") # Get File Metadata via external exiftool # requires installation # https://exiftool.org/index.html#running # if self.ui.chk_IgnMeta: # dont look at meta data of file just sort for name ''' fill dictionary ''' self.WorkData.update({ # int(time.mktime(time.strptime(dt, '%Y:%m:%d|%H:%M:%S'))) : basename : { #'itemNum' : str(count), 'fname' : basename, 'fileExt' : self.get_extension(str(fname)), 'tmpName' : self.TmpDir + '/PICTtmp-' + next(tempfile._get_candidate_names()), 'rename' : True, 'date' : "", 'time' : "" } }) count += 1 else: # Normal way looking at Metadata to get file sosted with creation time # exiftool has produced with json and TAGS if basename.endswith(EXIF_MOVIE_EXT): EXIF_TAGS = EXIF_MOVIE_TAGS else: EXIF_TAGS = EXIF_PHOTO_TAGS args=[EXIFTOOL, "-json", *EXIF_TAGS, os.path.join(self.WorkDir,basename)] LOGGER.debug(str(args)) process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) res=json.loads(process.stdout.read()) LOGGER.debug(json.dumps(res,indent=2)) dat = res[0] # reset of all tag values dt = odate = fmdate = fcdate = mcdate = None if basename.endswith(EXIF_MOVIE_EXT): # MOVIEs try: fmdate = dat['FileModifyDate'] except KeyError: LOGGER.debug(f"File {basename} does not contain tag FileModifyDate") except Exception as e: LOGGER.error(f"File {basename} parsing error {e}") continue # for fname in files[0]: try: mcdate = dat['MediaCreateDate'] except KeyError: LOGGER.debug(f"File {basename} does not contain tag MediaCreateDate") except Exception as e: LOGGER.error(f"File {basename} parsing error {e}") continue if not mcdate and not fmdate: LOGGER.error(f"File {basename} does NOT contain expected metadata tags, please verify results") continue # for fname in files[0]: if fmdate: try: # dt = f"{mcdate[0]}:{mcdate[1]}:{mcdate[2].split(' ')[0]}|{mcdate[2].split(' ')[1]}:{mcdate[3]}:{(mcdate[4].split('+'))[0]}".strip() pd = int(time.mktime(time.strptime(fmdate, '%Y:%m:%d %H:%M:%S%z'))) pod = datetime.fromtimestamp(pd) dt = pod.strftime('%Y:%m:%d|%H:%M:%S') except Exception as e: LOGGER.error(f"File {basename} parsing tag value: {e} ...Skipping...") continue # for fname in files[0]: else: try: # dt = f"{mcdate[0]}:{mcdate[1]}:{mcdate[2].split(' ')[0]}|{mcdate[2].split(' ')[1]}:{mcdate[3]}:{(mcdate[4].split('+'))[0]}".strip() pd = int(time.mktime(time.strptime(mcdate, '%Y:%m:%d %H:%M:%S'))) pod = datetime.fromtimestamp(pd) dt = pod.strftime('%Y:%m:%d|%H:%M:%S') except Exception as e: LOGGER.error(f"File {basename} parsing tag value: {e} ...Skipping...") continue # for fname in files[0]: else: # PICTUREs try: odate = dat['DateTimeOriginal'] except KeyError: LOGGER.debug(f"File {basename} does not contain tag DateTimeOriginal") except Exception as e: LOGGER.error(f"File {basename} parsing error {e}") continue # for fname in files[0]: try: fcdate = dat['FileCreateDate'] except KeyError: LOGGER.debug(f"File {basename} does not contain tag FileCreateDate") except Exception as e: LOGGER.error(f"File {basename} parsing error {e}") continue # for fname in files[0]: try: fmdate = dat['FileModifyDate'] except KeyError: LOGGER.debug(f"File {basename} does not contain tag FileModifyDate") except Exception as e: LOGGER.error(f"File {basename} parsing error {e}") continue # for fname in files[0]: if not odate and not fcdate and not fmdate: LOGGER.error(f"File {basename} does NOT contain expected metadata tags, please verify results") continue # for fname in files[0]: if odate: #take DateTimeOriginal od = int(time.mktime(time.strptime(odate, '%Y:%m:%d %H:%M:%S'))) ood = datetime.fromtimestamp(od) dt = ood.strftime('%Y:%m:%d|%H:%M:%S') elif fcdate and fmdate: # take older between FileCreateDate and FileModifyDate fcfmdate = min(int(time.mktime(time.strptime(fcdate, '%Y:%m:%d %H:%M:%S%z'))), int(time.mktime(time.strptime(fmdate, '%Y:%m:%d %H:%M:%S%z')))) ofcfmdate = datetime.fromtimestamp(fcfmdate) dt = ofcfmdate.strftime('%Y:%m:%d|%H:%M:%S') elif fcdate and not fmdate: # take fcdate fc = int(time.mktime(time.strptime(fcdate, '%Y:%m:%d %H:%M:%S%z'))) dt = fc.strftime('%Y:%m:%d|%H:%M:%S') else: # take fmdate fm = int(time.mktime(time.strptime(fmdate, '%Y:%m:%d %H:%M:%S%z'))) dt = fm.strftime('%Y:%m:%d|%H:%M:%S') if not dt: LOGGER.error(f"File {basename} does NOT contain expected metadata tags, please verify results") continue # for fname in files[0]: # check if same modification time exist so it loops adding one second to modification time adt = int(time.mktime(time.strptime(dt, '%Y:%m:%d|%H:%M:%S'))) while adt in self.WorkData.keys(): adt+=1 LOGGER.info(f"Time: {dt} --> {basename}") ''' fill dictionary ''' self.WorkData.update({ # int(time.mktime(time.strptime(dt, '%Y:%m:%d|%H:%M:%S'))) : adt : { #'itemNum' : str(count), 'fname' : basename, 'fileExt' : self.get_extension(str(fname)), 'tmpName' : self.TmpDir + '/PICTtmp-' + next(tempfile._get_candidate_names()), 'rename' : True, 'date': dt.split('|')[0], 'time': dt.split('|')[1] } }) count += 1 # end of sorting with meta data self.pb.end() # print unsorted dictionary # print(f"{json.dumps(self.WorkData, indent=4)}") # transpose disctionary and sort it by date of creation of the file flts = dict(sorted(self.WorkData.items())) # self.WorkData = flts # print(f"{json.dumps(self.WorkData, indent=4)}") for _,v in self.WorkData.items(): ''' add an item to Qtree including resizing of pictures ''' # self.add_Qtree_item(f"{v['fname']}-->{v['date']}|{v['time']}") self.add_Qtree_item(f"{v['fname']}") return def click_Renumber(self): ''' renumber and create a new WorkData for next phases ''' newWorkData = {} ''' only if WorkData Contains elements ''' nf = len(self.WorkData) if nf > 0: self.pb.start(steps=nf * 2) ''' for all files move to temp to avoid overlapping''' for _,v in self.WorkData.items(): LOGGER.debug(f"Moving file {v}") if v['rename']: self.pb.incr() try: tmpfile = v['tmpName'] origfile = self.WorkDir + '/' + v['fname'] except Exception as e: LOGGER.error(f"Parsing file attributes [{v['fname']} : {e} ... Skipping ...") else: try: shutil.move(origfile, tmpfile) #os.rename(origfile, tmpfile) except Exception as e: LOGGER.error(f"Renaming file [{origfile}] to tmpfile: {tmpfile} : {e} ... Skipping ...") else: # LOGGER.debug(f"Origfile [{origfile}] ==> TmpFile [{tmpfile}]') pass ''' for all files move to current directory with new name''' count = 1 for _,v in self.WorkData.items(): LOGGER.debug(f"Moving file {v}") if v['rename']: self.pb.incr() try: tmpfile = v['tmpName'] extension = v['fileExt'] #count = int(self.WorkData[fname]['itemNum']) except Exception as e: LOGGER.error(f"Parsing file attributes [{v['fname']} : {e} ... Skipping ...") else: targetfile = self.WorkDir + '/' + self.fullName + '-{0:04d}'.format(count) + extension targetfname = self.fullName + '-{0:04d}'.format(count) + extension try: shutil.move(tmpfile, targetfile) #os.rename(tmpfile, targetfile) except Exception as e: LOGGER.error(f"Renaming tmpfile [{tmpfile}] to: {targetfname} : {e} ... Skipping ...") else: #LOGGER.debug('TmpFile [%s] ==> TargetFIle [%s]' % (tmpfile, targetfile)) LOGGER.info(f"Renamed [{tmpfile}] ==> [{targetfile}]") pass ''' fill dictionary ''' newWorkData.update({ targetfname : { 'itemNum' : str(count), 'fileExt' : self.get_extension(str(targetfname)), 'tmpName' : self.TmpDir + '/PICTtmp-' + next(tempfile._get_candidate_names()), 'rename' : True }, }) count += int(self.ui.cmb_Step.currentText()) self.pb.end() ''' also rename directory ''' basedir = os.path.dirname(self.WorkDir) olddir = self.WorkDir newdir = basedir + '/' + self.fullName try: shutil.move(olddir, newdir) except Exception as e: LOGGER.error(f"Rename of directory to [{basedir}] failed {e} ...Skipping...") ''' sel new Current working directory and Workdata to new dictionary ''' self.WorkDir = newdir os.chdir(self.WorkDir) self.WorkData = newWorkData ''' empties Qtree ''' self.ui.tree_OUT.clear() self.ui.tree_OUT.setColumnCount(len(FILE_TREE_CNTL)) self.ui.tree_OUT.setHeaderLabels(self.set_qtree_headers(FILE_TREE_CNTL)) self.ui.tree_OUT.setUniformRowHeights(True) ''' and refill it again ''' nf = len (self.WorkData) WorkData_s = OrderedDict(self.WorkData) # redraw the dialog self.pb.start(steps=nf) for fname in WorkData_s: self.pb.incr() self.add_Qtree_item(self.WorkDir + '/' + fname) self.pb.end() return def add_Qtree_item(self, fname): basefile = os.path.basename(str(fname)) if self.ui.chk_Preview.isChecked(): ''' review is activated need to elaborate thumbnail ''' item = self.QaddFILEChild(self.ui.tree_OUT, basefile, str(fname)) try: ''' rezize file ''' #img = QtGui.QImage(fname) # qim = img.scaled(64, 64, QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.SmoothTransformation) # qim = img.scaled(64, 64) # pict = QPixmap.fromImage(qim) pict = QPixmap(fname) except Exception as e: LOGGER.error(f"Unable to process image [{fname}] {e} ... Skipping ...") label = QLabel() label.setText("Preview Error") else: ''' create a Label with picture as background ''' label = QLabel() label.setPixmap(pict) ''' add column 2 a Label with picture in pixmap ''' item.treeWidget().setItemWidget(item, 2, label) else: item = self.QaddFILEChild(self.ui.tree_OUT, basefile, str(fname)) return item def get_extension(self, fname): basename = os.path.basename(fname) # os independent ext = '.'.join(basename.split('.')[1:]) return '.' + ext if ext else None def change_date(self): self.day = self.ui.date_Day.date().day() self.month = self.ui.date_Day.date().month() self.year = self.ui.date_Day.date().year() self.ui.cmb_Day.setCurrentIndex(self.day - 1) self.ui.cmb_Month.setCurrentIndex(self.month - 1) self.ui.cmb_Year.setCurrentIndex(self.year - YEARS_RANGE[0] ) ''' set fullname ''' self.set_fullName() return def changed_Name(self): if self.ineditname: return else: tc = self.ui.txt_Name.textCursor() name = str(self.ui.txt_Name.toPlainText()) filename = "".join(i for i in name if i not in NON_FILE_CHARACTERS) self.ineditname = True self.ui.txt_Name.setPlainText(filename) self.ui.txt_Name.setTextCursor(tc) self.ineditname = False self.name = filename self.set_fullName() return def selected_rbtn_Movies(self): self.filecriteria = MOVIE_filter return def selected_rbtn_Photos(self): self.filecriteria = PHOTO_filter return def choose_Day(self): self.day = int(self.ui.cmb_Day.currentText()) self.set_fullName() return def choose_Month(self): self.month = int(self.ui.cmb_Month.currentText()) self.set_fullName() return def choose_Year(self): self.year = int(self.ui.cmb_Year.currentText()) self.set_fullName() return def set_qtree_headers(self, tree_ctnl): tree_HEADERS = [] i = 0 for treeitem in tree_ctnl: tree_HEADERS.append(treeitem[0]) self.ui.tree_OUT.setColumnWidth(i, treeitem[1]) i += 1 return tree_HEADERS def set_fullName(self): self.fullName = '{0:4d}{1:02d}{2:02d}-{3:s}'.format(self.year, self.month, self.day, self.name) self.ui.txt_FullName.setText(self.fullName) if len(self.fullName) > 9: self.ui.btn_Renumber.setEnabled(True) else: self.ui.btn_Renumber.setEnabled(False) # def msg_print(self, msg): # self.ui.txt_Error.setText(str(msg)) # self.repaint() ''' ========================= Other Functions ===================================================''' ''' progress bar class and methods ''' class PB(): def __init__(self, progressbar, mi = 0, ma = 100 ): # super(PB, self).__init__(parent) self.val = 0 self.min = mi self.max = ma self.steps = 10 self.step = (self.max - self.min)/self.steps self.progressbar = progressbar self.progressbar.setMinimum(self.min) self.progressbar.setMaximum(self.max) def start(self, steps=10): self.steps = (steps if steps > 0 else 10) self.progressbar.setVisible(True) self.progressbar.setValue(0) def end(self): self.progressbar.setValue(self.max) # start a timer to make progressbar invisible threading.Timer(3,self.clear).start() def clear(self): self.progressbar.setVisible(False) def upd(self, val): self.val = val self.progressbar.setValue(self.val) def incr(self): actv = self.progressbar.value() newv = actv + self.step if newv <= self.max: self.progressbar.setValue(int(newv)) '''==========================================================================================================================''' ''' Main Program ''' if __name__ == '__main__': ''' Enabling Logger ''' prog_name = os.path.basename(sys.argv[0:][0]) LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) LOGGER.propagate = False logging.captureWarnings(True) formatter = logging.Formatter(LOG_FORMAT, LOG_TIME_FORMAT) ''' File logging ''' log_name = "LOG-" + prog_name + ".log" fh = logging.FileHandler(log_name) fh.setLevel(FILE_DEBUG) fh.setFormatter(formatter) LOGGER.addHandler(fh) ''' Console logging ''' cl = logging.StreamHandler(sys.stdout) cl.setLevel(CONSOLE_DEBUG) cl.setFormatter(formatter) LOGGER.addHandler(cl) time.sleep(1) LOGGER.info("%s: Ver %s - Date: %s" % (prog_name, mod_ver, mod_dver)) ''' Open window and give control to QTPY ''' app = QApplication(sys.argv) window = main() window.show() sys.exit(app.exec())