Example #1
0
class Config(QDialog):
    '''
    Configuration dialog for single book conversion. If accepted, has the
    following important attributes

    output_format - Output format (without a leading .)
    input_format  - Input format (without a leading .)
    opf_path - Path to OPF file with user specified metadata
    cover_path - Path to user specified cover (can be None)
    recommendations - A pickled list of 3 tuples in the same format as the
    recommendations member of the Input/Output plugins.
    '''
    def __init__(self,
                 parent,
                 db,
                 book_id,
                 preferred_input_format=None,
                 preferred_output_format=None):
        QDialog.__init__(self, parent)
        self.setupUi()
        self.opt_individual_saved_settings.setVisible(False)
        self.db, self.book_id = db, book_id

        self.setup_input_output_formats(self.db, self.book_id,
                                        preferred_input_format,
                                        preferred_output_format)
        self.setup_pipeline()

        self.input_formats.currentIndexChanged[native_string_type].connect(
            self.setup_pipeline)
        self.output_formats.currentIndexChanged[native_string_type].connect(
            self.setup_pipeline)
        self.groups.setSpacing(5)
        self.groups.activated[(QModelIndex)].connect(self.show_pane)
        self.groups.clicked[(QModelIndex)].connect(self.show_pane)
        self.groups.entered[(QModelIndex)].connect(self.show_group_help)
        rb = self.buttonBox.button(self.buttonBox.RestoreDefaults)
        rb.setText(_('Restore &defaults'))
        rb.clicked.connect(self.restore_defaults)
        self.groups.setMouseTracking(True)
        geom = gprefs.get('convert_single_dialog_geom', None)
        if geom:
            self.restoreGeometry(geom)
        else:
            self.resize(self.sizeHint())

    def setupUi(self):
        self.setObjectName("Dialog")
        self.resize(1024, 700)
        self.setWindowIcon(QIcon(I('convert.png')))
        self.gridLayout = QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.horizontalLayout = QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.input_label = QLabel(self)
        self.input_label.setObjectName("input_label")
        self.horizontalLayout.addWidget(self.input_label)
        self.input_formats = QComboBox(self)
        self.input_formats.setSizeAdjustPolicy(
            QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.input_formats.setMinimumContentsLength(5)
        self.input_formats.setObjectName("input_formats")
        self.horizontalLayout.addWidget(self.input_formats)
        self.opt_individual_saved_settings = QCheckBox(self)
        self.opt_individual_saved_settings.setObjectName(
            "opt_individual_saved_settings")
        self.horizontalLayout.addWidget(self.opt_individual_saved_settings)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.label_2 = QLabel(self)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout.addWidget(self.label_2)
        self.output_formats = QComboBox(self)
        self.output_formats.setSizeAdjustPolicy(
            QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.output_formats.setMinimumContentsLength(5)
        self.output_formats.setObjectName("output_formats")
        self.horizontalLayout.addWidget(self.output_formats)
        self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 2)
        self.groups = QListView(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(1)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.groups.sizePolicy().hasHeightForWidth())
        self.groups.setSizePolicy(sizePolicy)
        self.groups.setTabKeyNavigation(True)
        self.groups.setIconSize(QSize(48, 48))
        self.groups.setWordWrap(True)
        self.groups.setObjectName("groups")
        self.gridLayout.addWidget(self.groups, 1, 0, 3, 1)
        self.scrollArea = QScrollArea(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(4)
        sizePolicy.setVerticalStretch(10)
        sizePolicy.setHeightForWidth(
            self.scrollArea.sizePolicy().hasHeightForWidth())
        self.scrollArea.setSizePolicy(sizePolicy)
        self.scrollArea.setFrameShape(QFrame.NoFrame)
        self.scrollArea.setLineWidth(0)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QWidget()
        self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 810, 494))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.verticalLayout_3 = QVBoxLayout(self.scrollAreaWidgetContents)
        self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.stack = QStackedWidget(self.scrollAreaWidgetContents)
        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.stack.sizePolicy().hasHeightForWidth())
        self.stack.setSizePolicy(sizePolicy)
        self.stack.setObjectName("stack")
        self.page = QWidget()
        self.page.setObjectName("page")
        self.stack.addWidget(self.page)
        self.page_2 = QWidget()
        self.page_2.setObjectName("page_2")
        self.stack.addWidget(self.page_2)
        self.verticalLayout_3.addWidget(self.stack)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.gridLayout.addWidget(self.scrollArea, 1, 1, 1, 1)
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel
                                          | QDialogButtonBox.Ok
                                          | QDialogButtonBox.RestoreDefaults)
        self.buttonBox.setObjectName("buttonBox")
        self.gridLayout.addWidget(self.buttonBox, 3, 1, 1, 1)
        self.help = QTextEdit(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.help.sizePolicy().hasHeightForWidth())
        self.help.setSizePolicy(sizePolicy)
        self.help.setMaximumHeight(80)
        self.help.setObjectName("help")
        self.gridLayout.addWidget(self.help, 2, 1, 1, 1)
        self.input_label.setBuddy(self.input_formats)
        self.label_2.setBuddy(self.output_formats)
        self.input_label.setText(_("&Input format:"))
        self.opt_individual_saved_settings.setText(
            _("Use &saved conversion settings for individual books"))
        self.label_2.setText(_("&Output format:"))

        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

    def sizeHint(self):
        desktop = QCoreApplication.instance().desktop()
        geom = desktop.availableGeometry(self)
        nh, nw = max(300, geom.height() - 100), max(400, geom.width() - 70)
        return QSize(nw, nh)

    def restore_defaults(self):
        delete_specifics(self.db, self.book_id)
        self.setup_pipeline()

    @property
    def input_format(self):
        return unicode_type(self.input_formats.currentText()).lower()

    @property
    def output_format(self):
        return unicode_type(self.output_formats.currentText()).lower()

    @property
    def manually_fine_tune_toc(self):
        for i in range(self.stack.count()):
            w = self.stack.widget(i)
            if hasattr(w, 'manually_fine_tune_toc'):
                return w.manually_fine_tune_toc.isChecked()

    def setup_pipeline(self, *args):
        oidx = self.groups.currentIndex().row()
        input_format = self.input_format
        output_format = self.output_format
        self.plumber = create_dummy_plumber(input_format, output_format)

        def widget_factory(cls):
            return cls(self.stack, self.plumber.get_option_by_name,
                       self.plumber.get_option_help, self.db, self.book_id)

        self.mw = widget_factory(MetadataWidget)
        self.setWindowTitle(
            _('Convert') + ' ' + unicode_type(self.mw.title.text()))
        lf = widget_factory(LookAndFeelWidget)
        hw = widget_factory(HeuristicsWidget)
        sr = widget_factory(SearchAndReplaceWidget)
        ps = widget_factory(PageSetupWidget)
        sd = widget_factory(StructureDetectionWidget)
        toc = widget_factory(TOCWidget)
        from calibre.gui2.actions.toc_edit import SUPPORTED
        toc.manually_fine_tune_toc.setVisible(
            output_format.upper() in SUPPORTED)
        debug = widget_factory(DebugWidget)

        output_widget = self.plumber.output_plugin.gui_configuration_widget(
            self.stack, self.plumber.get_option_by_name,
            self.plumber.get_option_help, self.db, self.book_id)
        input_widget = self.plumber.input_plugin.gui_configuration_widget(
            self.stack, self.plumber.get_option_by_name,
            self.plumber.get_option_help, self.db, self.book_id)
        while True:
            c = self.stack.currentWidget()
            if not c:
                break
            self.stack.removeWidget(c)

        widgets = [self.mw, lf, hw, ps, sd, toc, sr]
        if input_widget is not None:
            widgets.append(input_widget)
        if output_widget is not None:
            widgets.append(output_widget)
        widgets.append(debug)
        for w in widgets:
            self.stack.addWidget(w)
            w.set_help_signal.connect(self.help.setPlainText)

        self._groups_model = GroupModel(widgets)
        self.groups.setModel(self._groups_model)

        idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0
        self.groups.setCurrentIndex(self._groups_model.index(idx))
        self.stack.setCurrentIndex(idx)
        try:
            shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True)
        except:
            pass

    def setup_input_output_formats(self, db, book_id, preferred_input_format,
                                   preferred_output_format):
        if preferred_output_format:
            preferred_output_format = preferred_output_format.upper()
        output_formats = get_output_formats(preferred_output_format)
        input_format, input_formats = get_input_format_for_book(
            db, book_id, preferred_input_format)
        preferred_output_format = preferred_output_format if \
            preferred_output_format in output_formats else \
            sort_formats_by_preference(output_formats,
                    [prefs['output_format']])[0]
        self.input_formats.addItems(
            (unicode_type(x.upper()) for x in input_formats))
        self.output_formats.addItems(
            (unicode_type(x.upper()) for x in output_formats))
        self.input_formats.setCurrentIndex(input_formats.index(input_format))
        self.output_formats.setCurrentIndex(
            output_formats.index(preferred_output_format))

    def show_pane(self, index):
        self.stack.setCurrentIndex(index.row())

    def accept(self):
        recs = GuiRecommendations()
        for w in self._groups_model.widgets:
            if not w.pre_commit_check():
                return
            x = w.commit(save_defaults=False)
            recs.update(x)
        self.opf_file, self.cover_file = self.mw.opf_file, self.mw.cover_file
        self._recommendations = recs
        if self.db is not None:
            recs['gui_preferred_input_format'] = self.input_format
            save_specifics(self.db, self.book_id, recs)
        self.break_cycles()
        QDialog.accept(self)

    def reject(self):
        self.break_cycles()
        QDialog.reject(self)

    def done(self, r):
        if self.isVisible():
            gprefs['convert_single_dialog_geom'] = \
                bytearray(self.saveGeometry())
        return QDialog.done(self, r)

    def break_cycles(self):
        for i in range(self.stack.count()):
            w = self.stack.widget(i)
            w.break_cycles()

    @property
    def recommendations(self):
        recs = [(k, v, OptionRecommendation.HIGH)
                for k, v in self._recommendations.items()]
        return recs

    def show_group_help(self, index):
        widget = self._groups_model.widgets[index.row()]
        self.help.setPlainText(widget.HELP)
Example #2
0
    def FillPhotosByFilter(self, filter):

        # it's tempting to think that we could use the insertPhotoIntoTable routine,
        # but we can't here, because if we're filling photos by filter, we already know
        # each photo's meta data.  The insertPhotoIntoTable routine tries to guess the
        # location, time, species, etc. from the photo file's embedded meta data.

        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

        self.scaleMe()
        self.resizeMe()

        self.fillingCombos = True

        # save the filter settings passed to this routine to the form itself for future use
        self.filter = filter

        photoSightings = self.mdiParent.db.GetSightingsWithPhotos(filter)

        if len(photoSightings) == 0:
            return False

        row = 0

        # count photos for message display
        photoCount = 0
        for s in photoSightings:
            photoCount = photoCount + len(s["photos"])
        photoCount = str(photoCount)

        for s in photoSightings:
            for p in s["photos"]:

                self.mdiParent.lblStatusBarMessage.setVisible(True)
                self.mdiParent.lblStatusBarMessage.setText(
                    "Processing photo " + str(row + 1) + " of " + photoCount)

                # p is a filename. Use it to add the image to the label as a pixmap
                buttonPhoto = QPushButton()
                buttonPhoto.setMinimumHeight(281)
                buttonPhoto.setMinimumWidth(500)

                # get thumbnail from file to display
                pixMap = self.GetPixmapForThumbnail(p["fileName"])

                buttonPhoto.setIcon(QIcon(pixMap))

                # size to 500x281
                buttonPhoto.setIconSize(QSize(500, 281))
                buttonPhoto.setStyleSheet(
                    "QPushButton {background-color: #343333; border: 0px}")

                # display thumbnail to new row in grid
                self.gridPhotos.addWidget(buttonPhoto, row, 0)

                # set up layout in second column of row to house combo boxes
                # give each object a name according to the row so we can access them later
                container = QWidget()
                container.setObjectName("container" + str(row))
                detailsLayout = QVBoxLayout(container)
                detailsLayout.setObjectName("layout" + str(row))
                detailsLayout.setAlignment(Qt.AlignTop)
                self.gridPhotos.addWidget(container, row, 1)

                # create combo boxes for details
                # add connection for when user changes a combo box
                cboLocation = QComboBox()
                cboLocation.currentIndexChanged.connect(
                    partial(self.cboLocationChanged, row))

                cboDate = QComboBox()
                cboDate.currentIndexChanged.connect(
                    partial(self.cboDateChanged, row))

                cboTime = QComboBox()
                cboTime.currentIndexChanged.connect(
                    partial(self.cboTimeChanged, row))

                cboCommonName = QComboBox()
                cboCommonName.currentIndexChanged.connect(
                    partial(self.cboCommonNameChanged, row))

                cboRating = QComboBox()
                cboRating.addItems(["Not Rated", "1", "2", "3", "4", "5"])
                cboRating.currentIndexChanged.connect(
                    partial(self.cboRatingChanged, row))

                # set stylesheet for cmbo boxes
                for c in [
                        cboLocation, cboDate, cboTime, cboCommonName, cboRating
                ]:
                    self.removeHighlight(c)

                # fill location combo box with all locations in db
                locations = self.mdiParent.db.locationList
                cboLocation.addItems(locations)

                # set location combo box to the photo's location
                index = cboLocation.findText(s["location"])
                if index >= 0:
                    cboLocation.setCurrentIndex(index)

                # fill date combo box with all dates associated with selected location
                filterForThisPhoto = code_Filter.Filter()
                filterForThisPhoto.setLocationName(s["location"])
                filterForThisPhoto.setLocationType("Location")
                dates = self.mdiParent.db.GetDates(filterForThisPhoto)
                cboDate.addItems(dates)

                # set date  combo box to the photo's associated date
                index = cboDate.findText(s["date"])
                if index >= 0:
                    cboDate.setCurrentIndex(index)

                # fill time combo box with all times associated with selected location and date
                filterForThisPhoto.setStartDate(s["date"])
                filterForThisPhoto.setEndDate(s["date"])
                startTimes = self.mdiParent.db.GetStartTimes(
                    filterForThisPhoto)
                cboTime.addItems(startTimes)

                # set time combo box to the photo's associated checklist time
                index = cboTime.findText(s["time"])
                if index >= 0:
                    cboTime.setCurrentIndex(index)

                # get common names from checklist associated with photo
                filterForThisPhoto.setChecklistID(s["checklistID"])
                commonNames = self.mdiParent.db.GetSpecies(filterForThisPhoto)

                cboCommonName.addItem("**Detach Photo**")
                cboCommonName.addItems(commonNames)

                # set combo box to common name
                index = cboCommonName.findText(s["commonName"])
                if index >= 0:
                    cboCommonName.setCurrentIndex(index)

                # set combo box to rating value
                index = int(p["rating"])
                cboRating.setCurrentIndex(index)

                # assign names to combo boxes for future access
                cboLocation.setObjectName("cboLocation" + str(row))
                cboDate.setObjectName("cboDate" + str(row))
                cboTime.setObjectName("cboTime" + str(row))
                cboCommonName.setObjectName("cboCommonName" + str(row))
                cboRating.setObjectName("cboRating" + str(row))

                # add combo boxes to the layout in second column
                detailsLayout.addWidget(cboLocation)
                detailsLayout.addWidget(cboDate)
                detailsLayout.addWidget(cboTime)
                detailsLayout.addWidget(cboCommonName)
                detailsLayout.addWidget(cboRating)

                # create and add resent button
                btnReset = QPushButton()
                btnReset.setText("Reset")
                btnReset.clicked.connect(partial(self.btnResetClicked, row))
                detailsLayout.addWidget(btnReset)

                # save meta data for future use when user clicks cbo boxes
                thisPhotoMetaData = {}
                thisPhotoMetaData["photoFileName"] = p["fileName"]
                thisPhotoMetaData["location"] = s["location"]
                thisPhotoMetaData["date"] = s["date"]
                thisPhotoMetaData["time"] = s["time"]
                thisPhotoMetaData["commonName"] = s["commonName"]
                thisPhotoMetaData["photoData"] = p
                thisPhotoMetaData["rating"] = p["rating"]

                self.metaDataByRow[row] = thisPhotoMetaData

                # initialize the "new" data so that there are values there, even if they're not really new
                # user can change the cbo boxes later, which will also change the "new" data
                self.saveNewMetaData(row)

                row = row + 1

                qApp.processEvents()

        self.mdiParent.lblStatusBarMessage.setText("")
        self.mdiParent.lblStatusBarMessage.setVisible(False)

        QApplication.processEvents()

        icon = QIcon()
        icon.addPixmap(QPixmap(":/icon_camera.png"), QIcon.Normal, QIcon.Off)
        self.setWindowIcon(icon)
        self.setWindowTitle("Manage Photos")

        self.fillingCombos = False

        QApplication.restoreOverrideCursor()

        # tell MainWindow that we succeeded filling the list
        return (True)
Example #3
0
class TempleItem (QtWidgets.QGraphicsItem):
    SIZE_MULTIPLICATOR = 2
    def __init__(self,view, scene_coord,temple,size,parent=None):
        super (TempleItem,self).__init__(parent)
        #self.polygon  = self.getTriangle (size)
        self.rotation = 0.0
        self.color = QColor(0,0,125)
        self.name_visible = True
        self.name = temple.name
        self.model = temple
        self.view = view
        self.size = QSize(32,32)
        self.scene_coord = scene_coord
        #set flags
        self.setFlag(QGraphicsItem.ItemIgnoresTransformations)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        
#     self.graphics_effect = QGraphicsColorizeEffect()
#     color = self.model.kingdom().color
#     self.graphics_effect.setColor(QColor(color.red(),color.green(),color.blue(),125))
#     self.setGraphicsEffect(self.graphics_effect )
#     self.graphics_effect.setEnabled(True)


        
        
    def boundingRect(self):
        return QtCore.QRectF(0,0,self.size.width(),self.size.height())
        



    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionHasChanged :
            self.model.position = value
            #pos = self.view.mapToScene(value.x(),value.y())
            lat,lon = self.scene_coord.SceneToLatLon(value.x(),value.y())
            self.model.changePosition (lat,lon)
            print ('new position ',self.model.position)
        else:
            return QGraphicsItem.itemChange(self,change,value)
    def paint (self,painter,option, widget):

        painter.setRenderHints(QtGui.QPainter.Antialiasing)
        painter.rotate(self.rotation)
        painter.translate(-16,-16) # taille fixe correspondant a la pixmap utilisee pour generer la geometry
        painter.scale(1.5,1.5)

        
        if self.isSelected() == True : 
            pen = QPen(QtCore.Qt.red)
            pen.setWidth(1)
        else:
            pen = QPen(QtCore.Qt.black)   
            pen.setWidth(1)
        painter.setPen(pen)
        radialGradient = QRadialGradient(self.size.width()/2, self.size.height()/2,self.size.width()*0.8, self.size.width(), self.size.height())
        radialGradient.setColorAt(0.0, QtCore.Qt.white)
        #radialGradient.setColorAt(0.2, self.heros.kingdom().color)
        if self.view.color_mode == ColorMode.Empire : 
            color = self.model.empire().color
        else :
            color = self.model.kingdom().color
        

        radialGradient.setColorAt(0.2, color)
        radialGradient.setColorAt(1.0, QtCore.Qt.black)
        painter.setBrush(QBrush(radialGradient))
        #brush = QBrush(self.color)
        #painter.setBrush(brush)
        geometry = self.model.empire().geometry
        #qDebug("info : map_item Temple : nombre de polygones %d"%len(geometry['polygon']))
        for p in geometry['polygon']:
            painter.drawPolygon(QPolygon(p))
#         
#         painter.drawPolygon(self.polygon)
#         path = os.path.join(Config().instance.path_to_icons(),'kingdom','32x32')
#         path+="/temple_artemis.png"
#         print ('path icon',path)
#         pix = QPixmap(path)
# 
#         painter.drawPixmap(-pix.width()/2.0,-pix.height()/2.0,pix)
        if self.name_visible == True :
            painter.setPen(QPen(QColor('black')))
            painter.translate(0,10)
            painter.drawText (self.boundingRect(),QtCore.Qt.AlignHCenter|QtCore.Qt.AlignBottom, self.name)

    def contextMenuEvent(self, event):
        menu = QMenu()
        testAction = QAction('Go Inside', None)
        testAction.triggered.connect(self.showTempleView)
        menu.addAction(testAction)
        menu.exec_(event.screenPos())
        event.accept()
    def showTempleView (self):
        self.frame = QFrame()
        self.frame.setWindowTitle(self.kingdom.name)
        self.frame.setObjectName("Frame")
        self.frame.resize(400, 300)
        self.frame.setFrameShape(QFrame.StyledPanel)
        self.frame.setFrameShadow(QFrame.Raised)
        self.verticalLayout = QVBoxLayout(self.frame)
        self.verticalLayout.setObjectName("verticalLayout")
        self.view = TempleView(self.frame)
        self.view.setTemple(self.kingdom)
        self.verticalLayout.addWidget(self.view)
        self.frame.show()
Example #4
0
    def insertPhotoIntoTable(self, row, photoData, photoMatchData, pixMap):

        QApplication.processEvents()

        self.fillingCombos = True

        photoLocation = photoMatchData["photoLocation"]
        photoDate = photoMatchData["photoDate"]
        photoTime = photoMatchData["photoTime"]
        photoCommonName = photoMatchData["photoCommonName"]

        # p is a filename. Use it to add the image to the label as a pixmap
        buttonPhoto = QPushButton()
        buttonPhoto.setMinimumHeight(281)
        buttonPhoto.setMinimumWidth(500)

        buttonPhoto.setIcon(QIcon(pixMap))

        # size to 500x281
        buttonPhoto.setIconSize(QSize(500, 281))
        buttonPhoto.setStyleSheet(
            "QPushButton {background-color: #343333; border: 0px}")

        # display thumbnail to new row in grid
        self.gridPhotos.addWidget(buttonPhoto, row, 0)

        # set up layout in second column of row to house combo boxes
        # give each object a name according to the row so we can access them later
        container = QWidget()
        container.setObjectName("container" + str(row))
        detailsLayout = QVBoxLayout(container)
        detailsLayout.setObjectName("layout" + str(row))
        detailsLayout.setAlignment(Qt.AlignTop)

        self.gridPhotos.addWidget(container, row, 1)

        # create combo boxes for details
        # add connection for when user changes a combo box
        cboLocation = QComboBox()
        cboLocation.currentIndexChanged.connect(
            partial(self.cboLocationChanged, row))

        cboDate = QComboBox()
        cboDate.currentIndexChanged.connect(partial(self.cboDateChanged, row))

        cboTime = QComboBox()
        cboTime.currentIndexChanged.connect(partial(self.cboTimeChanged, row))

        cboCommonName = QComboBox()
        cboCommonName.currentIndexChanged.connect(
            partial(self.cboCommonNameChanged, row))

        cboRating = QComboBox()
        cboRating.addItems(["Not Rated", "1", "2", "3", "4", "5"])
        cboRating.currentIndexChanged.connect(
            partial(self.cboRatingChanged, row))

        # set stylesheet for cbo boxes
        for c in [cboLocation, cboDate, cboTime, cboCommonName, cboRating]:
            self.removeHighlight(c)

        # fill location combo box with all locations in db
        locations = self.mdiParent.db.locationList
        cboLocation.addItems(locations)

        # set location combo box to the photo's location
        if photoLocation != "":
            index = cboLocation.findText(photoLocation)
            if index >= 0:
                cboLocation.setCurrentIndex(index)

                # fill date combo box with all dates associated with selected location
                filterForThisPhoto = code_Filter.Filter()
                filterForThisPhoto.setLocationName(photoLocation)
                filterForThisPhoto.setLocationType("Location")
                dates = self.mdiParent.db.GetDates(filterForThisPhoto)
                cboDate.addItems(dates)

                # set date  combo box to the photo's associated date
                index = cboDate.findText(photoDate)
                if index >= 0:
                    cboDate.setCurrentIndex(index)

                # fill time combo box with all times associated with selected location and date
                filterForThisPhoto.setStartDate(photoDate)
                filterForThisPhoto.setEndDate(photoDate)
                startTimes = self.mdiParent.db.GetStartTimes(
                    filterForThisPhoto)
                cboTime.addItems(startTimes)

                # set time combo box to the photo's associated checklist time
                index = cboTime.findText(photoTime)
                if index >= 0:
                    cboTime.setCurrentIndex(index)

                # get common names from checklist associated with photo
                filterForThisPhoto.setTime(photoTime)
                commonNames = self.mdiParent.db.GetSpecies(filterForThisPhoto)

                cboCommonName.addItem("**Detach Photo**")
                cboCommonName.addItems(commonNames)

                # set combo box to common name
                index = cboCommonName.findText(photoCommonName)
                if index >= 0:
                    cboCommonName.setCurrentIndex(index)

        # assign names to combo boxes for future access
        cboLocation.setObjectName("cboLocation" + str(row))
        cboDate.setObjectName("cboDate" + str(row))
        cboTime.setObjectName("cboTime" + str(row))
        cboCommonName.setObjectName("cboCommonName" + str(row))
        cboRating.setObjectName("cboRating" + str(row))

        lblFileName = QLabel()
        lblFileName.setText("File: " + os.path.basename(photoData["fileName"]))

        lblFileDate = QLabel()
        lblFileDate.setText("Date: " + photoData["date"])

        lblFileTime = QLabel()
        lblFileTime.setText("Time: " + photoData["time"])

        # add combo boxes to the layout in second column
        detailsLayout.addWidget(lblFileName)
        detailsLayout.addWidget(lblFileDate)
        detailsLayout.addWidget(lblFileTime)
        detailsLayout.addWidget(cboLocation)
        detailsLayout.addWidget(cboDate)
        detailsLayout.addWidget(cboTime)
        detailsLayout.addWidget(cboCommonName)
        detailsLayout.addWidget(cboRating)

        # create and add resent button
        btnReset = QPushButton()
        btnReset.setText("Reset")
        btnReset.clicked.connect(partial(self.btnResetClicked, row))
        detailsLayout.addWidget(btnReset)

        # save meta data for future use when user clicks cbo boxes
        thisPhotoMetaData = {}
        thisPhotoMetaData["photoFileName"] = photoData["fileName"]
        thisPhotoMetaData["location"] = photoLocation
        thisPhotoMetaData["date"] = photoDate
        thisPhotoMetaData["time"] = cboTime.currentText()
        thisPhotoMetaData["commonName"] = photoCommonName
        thisPhotoMetaData["photoData"] = photoData
        thisPhotoMetaData["rating"] = thisPhotoMetaData["photoData"]["rating"]

        self.metaDataByRow[row] = thisPhotoMetaData

        # initialize the "new" data so that there are values there, even if they're not really new
        # user can change the cbo boxes later, which will also change the "new" data
        self.saveNewMetaData(row)

        self.fillingCombos = False