def __init__(self, image_list, *arg, **kw): super(Descriptive, self).__init__(*arg, **kw) self.config_store = QtWidgets.QApplication.instance().config_store self.image_list = image_list self.form = QtWidgets.QFormLayout() self.setLayout(self.form) if qt_version_info >= (5, 0): self.trUtf8 = self.tr # construct widgets self.widgets = {} # title self.widgets['title'] = SingleLineEdit(spell_check=True) self.widgets['title'].editingFinished.connect(self.new_title) self.form.addRow(self.tr('Title / Object Name'), self.widgets['title']) # description self.widgets['description'] = MultiLineEdit(spell_check=True) self.widgets['description'].editingFinished.connect(self.new_description) self.form.addRow( self.tr('Description / Caption'), self.widgets['description']) # keywords self.widgets['keywords'] = KeywordsEditor() self.widgets['keywords'].editingFinished.connect(self.new_keywords) self.form.addRow(self.tr('Keywords'), self.widgets['keywords']) self.image_list.image_list_changed.connect(self.image_list_changed) # copyright self.widgets['copyright'] = LineEditWithAuto() self.widgets['copyright'].editingFinished.connect(self.new_copyright) self.widgets['copyright'].autoComplete.connect(self.auto_copyright) self.form.addRow(self.tr('Copyright'), self.widgets['copyright']) # creator self.widgets['creator'] = LineEditWithAuto() self.widgets['creator'].editingFinished.connect(self.new_creator) self.widgets['creator'].autoComplete.connect(self.auto_creator) self.form.addRow(self.tr('Creator / Artist'), self.widgets['creator']) # disable until an image is selected self.setEnabled(False)
def __init__(self, *arg, **kw): super(GoogleUploadConfig, self).__init__(*arg, **kw) self.setLayout(QtWidgets.QGridLayout()) self.layout().setContentsMargins(0, 0, 0, 0) # create new set new_set_button = QtWidgets.QPushButton(self.tr('New album')) new_set_button.clicked.connect(self.new_set) self.layout().addWidget(new_set_button, 2, 1) # list of sets widget sets_group = QtWidgets.QGroupBox(self.tr('Add to albums')) sets_group.setLayout(QtWidgets.QVBoxLayout()) scrollarea = QtWidgets.QScrollArea() scrollarea.setFrameStyle(QtWidgets.QFrame.NoFrame) scrollarea.setStyleSheet("QScrollArea { background-color: transparent }") self.sets_widget = QtWidgets.QWidget() self.sets_widget.setLayout(QtWidgets.QVBoxLayout()) self.sets_widget.layout().setSpacing(0) self.sets_widget.layout().setSizeConstraint( QtWidgets.QLayout.SetMinAndMaxSize) scrollarea.setWidget(self.sets_widget) self.sets_widget.setAutoFillBackground(False) sets_group.layout().addWidget(scrollarea) self.layout().addWidget(sets_group, 0, 2, 3, 1) self.layout().setColumnStretch(2, 1)
def __init__(self, *arg, **kw): super(EditSettings, self).__init__(*arg, **kw) self.config_store = QtWidgets.QApplication.instance().config_store self.setWindowTitle(self.tr('Photini: settings')) self.setLayout(QtWidgets.QVBoxLayout()) # main dialog area scroll_area = QtWidgets.QScrollArea() self.layout().addWidget(scroll_area) panel = QtWidgets.QWidget() panel.setLayout(QtWidgets.QFormLayout()) panel.layout().setRowWrapPolicy( max(QtWidgets.QFormLayout.WrapLongRows, panel.layout().rowWrapPolicy())) # apply & cancel buttons self.button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Apply | QtWidgets.QDialogButtonBox.Cancel) self.button_box.clicked.connect(self.button_clicked) self.layout().addWidget(self.button_box) # copyright holder name self.copyright_name = SingleLineEdit(spell_check=True) self.copyright_name.set_value( self.config_store.get('user', 'copyright_name', '')) panel.layout().addRow(self.tr('Copyright holder name'), self.copyright_name) # copyright text self.copyright_text = SingleLineEdit(spell_check=True) self.copyright_text.set_value( self.config_store.get('user', 'copyright_text', '')) self.copyright_text.setMinimumWidth( width_for_text(self.copyright_text, 'x' * 50)) panel.layout().addRow(self.tr('Copyright text'), self.copyright_text) # creator name self.creator_name = SingleLineEdit(spell_check=True) self.creator_name.set_value( self.config_store.get('user', 'creator_name', '')) panel.layout().addRow(self.tr('Creator name'), self.creator_name) # IPTC data force_iptc = eval(self.config_store.get('files', 'force_iptc', 'False')) self.write_iptc = QtWidgets.QCheckBox(self.tr('Always write')) self.write_iptc.setChecked(force_iptc) panel.layout().addRow(self.tr('IPTC metadata'), self.write_iptc) # sidecar files if_mode = eval(self.config_store.get('files', 'image', 'True')) sc_mode = self.config_store.get('files', 'sidecar', 'auto') if not if_mode: sc_mode = 'always' self.sc_always = QtWidgets.QRadioButton(self.tr('Always create')) self.sc_always.setChecked(sc_mode == 'always') panel.layout().addRow(self.tr('Sidecar files'), self.sc_always) self.sc_auto = QtWidgets.QRadioButton(self.tr('Create if necessary')) self.sc_auto.setChecked(sc_mode == 'auto') self.sc_auto.setEnabled(if_mode) panel.layout().addRow('', self.sc_auto) self.sc_delete = QtWidgets.QRadioButton( self.tr('Delete when possible')) self.sc_delete.setChecked(sc_mode == 'delete') self.sc_delete.setEnabled(if_mode) panel.layout().addRow('', self.sc_delete) # image file locking self.write_if = QtWidgets.QCheckBox(self.tr('(when possible)')) self.write_if.setChecked(if_mode) self.write_if.clicked.connect(self.new_write_if) panel.layout().addRow(self.tr('Write to image file'), self.write_if) # preserve file timestamps keep_time = eval( self.config_store.get('files', 'preserve_timestamps', 'False')) self.keep_time = QtWidgets.QCheckBox() self.keep_time.setChecked(keep_time) panel.layout().addRow(self.tr('Preserve file timestamps'), self.keep_time) # add panel to scroll area after its size is known scroll_area.setWidget(panel)
def __init__(self, parent=None): super(ImageList, self).__init__(parent) self.app = QtWidgets.QApplication.instance() self.drag_icon = None self.images = [] self.last_selected = None self.selection_anchor = None self.thumb_size = int( self.app.config_store.get('controls', 'thumb_size', '80')) layout = QtWidgets.QGridLayout() layout.setSpacing(0) layout.setRowStretch(0, 1) layout.setColumnStretch(3, 1) self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) # thumbnail display self.scroll_area = ScrollArea() self.scroll_area.dropped_images.connect(self.open_file_list) layout.addWidget(self.scroll_area, 0, 0, 1, 6) self.thumbnails = QtWidgets.QWidget() self.thumbnails.setLayout(FlowLayout()) self.scroll_area.setWidget(self.thumbnails) QtWidgets.QShortcut(QtGui.QKeySequence.MoveToPreviousChar, self.scroll_area, self.move_to_prev_thumb) QtWidgets.QShortcut(QtGui.QKeySequence.MoveToNextChar, self.scroll_area, self.move_to_next_thumb) QtWidgets.QShortcut(QtGui.QKeySequence.SelectPreviousChar, self.scroll_area, self.select_prev_thumb) QtWidgets.QShortcut(QtGui.QKeySequence.SelectNextChar, self.scroll_area, self.select_next_thumb) QtWidgets.QShortcut(QtGui.QKeySequence.SelectAll, self.scroll_area, self.select_all) # sort key selector layout.addWidget(QtWidgets.QLabel(self.tr('sort by: ')), 1, 0) self.sort_name = QtWidgets.QRadioButton(self.tr('file name')) self.sort_name.clicked.connect(self._new_sort_order) layout.addWidget(self.sort_name, 1, 1) self.sort_date = QtWidgets.QRadioButton(self.tr('date taken')) layout.addWidget(self.sort_date, 1, 2) self.sort_date.clicked.connect(self._new_sort_order) if eval(self.app.config_store.get('controls', 'sort_date', 'False')): self.sort_date.setChecked(True) else: self.sort_name.setChecked(True) # size selector layout.addWidget(QtWidgets.QLabel(self.tr('thumbnail size: ')), 1, 4) self.size_slider = QtWidgets.QSlider(Qt.Horizontal) self.size_slider.setTracking(False) self.size_slider.setRange(4, 9) self.size_slider.setPageStep(1) self.size_slider.setValue(self.thumb_size / 20) self.size_slider.setTickPosition(QtWidgets.QSlider.TicksBelow) self.size_slider.setMinimumWidth(140) self.size_slider.valueChanged.connect(self._new_thumb_size) layout.addWidget(self.size_slider, 1, 5)
def __init__(self, image_list, *arg, **kw): super(TabWidget, self).__init__(*arg, **kw) self.config_store = QtWidgets.QApplication.instance().config_store self.image_list = image_list self.setLayout(QtWidgets.QHBoxLayout()) self.widgets = {} self.date_widget = {} self.link_widget = {} # date and time date_group = QtWidgets.QGroupBox( translate('TechnicalTab', 'Date and time')) date_group.setLayout(QtWidgets.QFormLayout()) # create date and link widgets for master in self._master_slave: self.date_widget[master] = DateAndTimeWidget(master) self.date_widget[master].new_value.connect(self.new_date_value) slave = self._master_slave[master] if slave: self.link_widget[master, slave] = DateLink(master) self.link_widget[master, slave].new_link.connect(self.new_link) self.link_widget['taken', 'digitised'].setText( translate('TechnicalTab', "Link 'taken' and 'digitised'")) self.link_widget['digitised', 'modified'].setText( translate('TechnicalTab', "Link 'digitised' and 'modified'")) # add to layout date_group.layout().addRow(translate('TechnicalTab', 'Taken'), self.date_widget['taken']) date_group.layout().addRow('', self.link_widget['taken', 'digitised']) date_group.layout().addRow(translate('TechnicalTab', 'Digitised'), self.date_widget['digitised']) date_group.layout().addRow('', self.link_widget['digitised', 'modified']) date_group.layout().addRow(translate('TechnicalTab', 'Modified'), self.date_widget['modified']) # offset self.offset_widget = OffsetWidget() self.offset_widget.apply_offset.connect(self.apply_offset) date_group.layout().addRow( translate('TechnicalTab', 'Adjust times'), self.offset_widget) self.layout().addWidget(date_group) # other other_group = QtWidgets.QGroupBox(translate('TechnicalTab', 'Other')) other_group.setLayout(QtWidgets.QFormLayout()) other_group.layout().setFieldGrowthPolicy( QtWidgets.QFormLayout.AllNonFixedFieldsGrow) # orientation self.widgets['orientation'] = DropdownEdit() self.widgets['orientation'].add_item( translate('TechnicalTab', 'normal'), 1, ordered=False) self.widgets['orientation'].add_item( translate('TechnicalTab', 'rotate -90'), 6, ordered=False) self.widgets['orientation'].add_item( translate('TechnicalTab', 'rotate +90'), 8, ordered=False) self.widgets['orientation'].add_item( translate('TechnicalTab', 'rotate 180'), 3, ordered=False) self.widgets['orientation'].add_item( translate('TechnicalTab', 'reflect left-right'), 2, ordered=False) self.widgets['orientation'].add_item( translate('TechnicalTab', 'reflect top-bottom'), 4, ordered=False) self.widgets['orientation'].add_item( translate('TechnicalTab', 'reflect tr-bl'), 5, ordered=False) self.widgets['orientation'].add_item( translate('TechnicalTab', 'reflect tl-br'), 7, ordered=False) self.widgets['orientation'].new_value.connect(self.new_orientation) other_group.layout().addRow(translate( 'TechnicalTab', 'Orientation'), self.widgets['orientation']) # camera model self.widgets['camera_model'] = CameraList(extendable=True) self.widgets['camera_model'].setMinimumWidth( width_for_text(self.widgets['camera_model'], 'x' * 30)) self.widgets['camera_model'].new_value.connect(self.new_camera_model) self.widgets['camera_model'].extend_list.connect(self.add_camera_model) other_group.layout().addRow(translate( 'TechnicalTab', 'Camera'), self.widgets['camera_model']) # lens model self.widgets['lens_model'] = LensList(extendable=True) self.widgets['lens_model'].setMinimumWidth( width_for_text(self.widgets['lens_model'], 'x' * 30)) self.widgets['lens_model'].new_value.connect(self.new_lens_model) self.widgets['lens_model'].extend_list.connect(self.add_lens_model) other_group.layout().addRow(translate( 'TechnicalTab', 'Lens model'), self.widgets['lens_model']) # focal length self.widgets['focal_length'] = DoubleSpinBox() self.widgets['focal_length'].setMinimum(0.0) self.widgets['focal_length'].setSuffix(' mm') self.widgets['focal_length'].new_value.connect(self.new_focal_length) other_group.layout().addRow(translate( 'TechnicalTab', 'Focal length'), self.widgets['focal_length']) # 35mm equivalent focal length self.widgets['focal_length_35'] = IntSpinBox() self.widgets['focal_length_35'].setMinimum(0) self.widgets['focal_length_35'].setSuffix(' mm') self.widgets['focal_length_35'].new_value.connect(self.new_focal_length_35) other_group.layout().addRow(translate( 'TechnicalTab', '35mm equiv'), self.widgets['focal_length_35']) # aperture self.widgets['aperture'] = DoubleSpinBox() self.widgets['aperture'].setMinimum(0.0) self.widgets['aperture'].setPrefix('ƒ/') self.widgets['aperture'].new_value.connect(self.new_aperture) other_group.layout().addRow(translate( 'TechnicalTab', 'Aperture'), self.widgets['aperture']) self.layout().addWidget(other_group, stretch=1) # disable until an image is selected self.setEnabled(False)
def diff_metadata(self): dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(translate('ImageList', 'Metadata differences')) dialog.setLayout(QtWidgets.QVBoxLayout()) table = TableWidget() table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) table.setColumnCount(3) table.setHorizontalHeaderLabels([ translate('ImageList', 'new value'), translate('ImageList', 'undo'), translate('ImageList', 'old value') ]) labels = [] row = 0 undo = {} new_md = self.metadata old_md = Metadata(self.path) for key in ('title', 'description', 'keywords', 'rating', 'copyright', 'creator', 'date_taken', 'date_digitised', 'date_modified', 'orientation', 'lens_model', 'lens_make', 'lens_serial', 'lens_spec', 'focal_length', 'focal_length_35', 'aperture', 'latlong', 'altitude', 'location_taken', 'location_shown', 'thumbnail'): values = getattr(new_md, key), getattr(old_md, key) if values[0] == values[1]: continue table.setRowCount(row + 1) for n, value in enumerate(values): if not value: value = '' elif isinstance(value, MultiString): value = '\n'.join(value) else: value = six.text_type(value) item = QtWidgets.QTableWidgetItem(value) table.setItem(row, n * 2, item) undo[key] = QtWidgets.QTableWidgetItem() undo[key].setFlags(undo[key].flags() | Qt.ItemIsUserCheckable) undo[key].setCheckState(False) table.setItem(row, 1, undo[key]) labels.append(key) row += 1 table.setVerticalHeaderLabels(labels) table.resizeColumnsToContents() table.resizeRowsToContents() dialog.layout().addWidget(table) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return changed = False dirty = False for key, widget in undo.items(): if widget.checkState() == Qt.Checked: setattr(new_md, key, getattr(old_md, key)) changed = True else: dirty = True if not dirty: self.reload_metadata() elif changed: self.image_list.emit_selection()
def _replace_dialog(self, image): # has image already been uploaded? for keyword in image.metadata.keywords or []: name_pred, sep, photo_id = keyword.partition('=') if name_pred == ID_TAG: break else: # new upload return { 'set_metadata' : True, 'set_visibility': True, 'set_type' : True, 'set_albums' : True, 'replace_image' : False, 'new_photo' : True, }, None # get user preferences dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(translate('FlickrTab', 'Replace photo')) dialog.setLayout(QtWidgets.QVBoxLayout()) message = QtWidgets.QLabel(translate( 'FlickrTab', 'File {0} has already been uploaded to Flickr.' ' How would you like to update it?').format( os.path.basename(image.path))) message.setWordWrap(True) dialog.layout().addWidget(message) widget = {} widget['set_metadata'] = QtWidgets.QCheckBox( translate('FlickrTab', 'Replace metadata')) widget['set_visibility'] = QtWidgets.QCheckBox( translate('FlickrTab', 'Change who can see it')) widget['set_type'] = QtWidgets.QCheckBox( translate('FlickrTab', 'Change content type')) widget['set_albums'] = QtWidgets.QCheckBox( translate('FlickrTab', 'Change album membership')) widget['replace_image'] = QtWidgets.QCheckBox( translate('FlickrTab', 'Replace image')) widget['new_photo'] = QtWidgets.QCheckBox( translate('FlickrTab', 'Upload as new photo')) widget['new_photo'].toggled.connect(widget['set_metadata'].setDisabled) widget['new_photo'].toggled.connect(widget['set_visibility'].setDisabled) widget['new_photo'].toggled.connect(widget['set_type'].setDisabled) widget['new_photo'].toggled.connect(widget['set_albums'].setDisabled) no_upload = QtWidgets.QCheckBox( translate('FlickrTab', 'No image upload')) no_upload.setChecked(True) button_group = QtWidgets.QButtonGroup() button_group.addButton(widget['replace_image']) button_group.addButton(widget['new_photo']) button_group.addButton(no_upload) for key in ('set_metadata', 'set_visibility', 'set_type', 'set_albums', 'replace_image', 'new_photo'): dialog.layout().addWidget(widget[key]) widget[key].setChecked(self.replace_prefs[key]) dialog.layout().addWidget(no_upload) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return None, photo_id for key in self.replace_prefs: self.replace_prefs[key] = widget[key].isChecked() return dict(self.replace_prefs), photo_id
def __init__(self, image_list, *arg, **kw): super(Technical, self).__init__(*arg, **kw) self.image_list = image_list self.setLayout(QtWidgets.QGridLayout()) self.widgets = {} self.date_widget = {} self.link_widget = {} # store lens data in another object self.lens_data = LensData() # date and time date_group = QtWidgets.QGroupBox(self.tr('Date and time')) date_group.setLayout(QtWidgets.QFormLayout()) # taken self.date_widget['taken'] = DateAndTimeWidget() self.date_widget['taken'].new_value.connect(self.new_date_taken) date_group.layout().addRow(self.tr('Taken'), self.date_widget['taken']) # link taken & digitised self.link_widget['taken', 'digitised'] = QtWidgets.QCheckBox( self.tr("Link 'taken' and 'digitised'")) self.link_widget['taken', 'digitised'].clicked.connect(self.new_link_digitised) date_group.layout().addRow('', self.link_widget['taken', 'digitised']) # digitised self.date_widget['digitised'] = DateAndTimeWidget() self.date_widget['digitised'].new_value.connect( self.new_date_digitised) date_group.layout().addRow(self.tr('Digitised'), self.date_widget['digitised']) # link digitised & modified self.link_widget['digitised', 'modified'] = QtWidgets.QCheckBox( self.tr("Link 'digitised' and 'modified'")) self.link_widget['digitised', 'modified'].clicked.connect(self.new_link_modified) date_group.layout().addRow('', self.link_widget['digitised', 'modified']) # modified self.date_widget['modified'] = DateAndTimeWidget() self.date_widget['modified'].new_value.connect(self.new_date_modified) date_group.layout().addRow(self.tr('Modified'), self.date_widget['modified']) # offset self.offset_widget = OffsetWidget() self.offset_widget.apply_offset.connect(self.apply_offset) date_group.layout().addRow(self.tr('Adjust times'), self.offset_widget) self.layout().addWidget(date_group, 0, 0) # other other_group = QtWidgets.QGroupBox(self.tr('Other')) other_group.setLayout(QtWidgets.QFormLayout()) # orientation self.widgets['orientation'] = DropdownEdit() self.widgets['orientation'].add_item(self.tr('normal'), 1) self.widgets['orientation'].add_item(self.tr('rotate -90'), 6) self.widgets['orientation'].add_item(self.tr('rotate +90'), 8) self.widgets['orientation'].add_item(self.tr('rotate 180'), 3) self.widgets['orientation'].add_item(self.tr('reflect left-right'), 2) self.widgets['orientation'].add_item(self.tr('reflect top-bottom'), 4) self.widgets['orientation'].add_item(self.tr('reflect tr-bl'), 5) self.widgets['orientation'].add_item(self.tr('reflect tl-br'), 7) self.widgets['orientation'].new_value.connect(self.new_orientation) other_group.layout().addRow(self.tr('Orientation'), self.widgets['orientation']) # lens model self.widgets['lens_model'] = DropdownEdit() self.widgets['lens_model'].setContextMenuPolicy(Qt.CustomContextMenu) self.widgets['lens_model'].add_item(self.tr('<define new lens>'), '<add lens>') for model in self.lens_data.lenses: self.widgets['lens_model'].add_item(model, model) self.widgets['lens_model'].new_value.connect(self.new_lens_model) self.widgets['lens_model'].customContextMenuRequested.connect( self.remove_lens_model) other_group.layout().addRow(self.tr('Lens model'), self.widgets['lens_model']) # lens specification self.widgets['lens_spec'] = LensSpecWidget() other_group.layout().addRow(self.tr('Lens details'), self.widgets['lens_spec']) # focal length self.widgets['focal_length'] = FloatEdit() self.widgets['focal_length'].validator().setBottom(0.1) self.widgets['focal_length'].editingFinished.connect( self.new_focal_length) other_group.layout().addRow(self.tr('Focal length (mm)'), self.widgets['focal_length']) # 35mm equivalent focal length self.widgets['focal_length_35'] = IntEdit() self.widgets['focal_length_35'].validator().setBottom(1) self.widgets['focal_length_35'].editingFinished.connect( self.new_focal_length_35) other_group.layout().addRow(self.tr('35mm equiv (mm)'), self.widgets['focal_length_35']) # aperture self.widgets['aperture'] = FloatEdit() self.widgets['aperture'].validator().setBottom(0.1) self.widgets['aperture'].editingFinished.connect(self.new_aperture) other_group.layout().addRow(self.tr('Aperture f/'), self.widgets['aperture']) self.layout().addWidget(other_group, 0, 1) self.layout().setColumnStretch(0, 1) self.layout().setColumnStretch(1, 1) # disable until an image is selected self.setEnabled(False)
def __init__(self, image_list, parent=None): super(PhotiniMap, self).__init__(parent) self.app = QtWidgets.QApplication.instance() self.image_list = image_list self.geocode_cache = OrderedDict() name = self.__module__.split('.')[-1] self.api_key = key_store.get(name, 'api_key') self.search_key = key_store.get('opencage', 'api_key') self.script_dir = pkg_resources.resource_filename( 'photini', 'data/' + name + '/') self.drag_icon = QtGui.QPixmap( os.path.join(self.script_dir, '../map_pin_grey.png')) self.drag_hotspot = 11, 35 self.search_string = None self.map_loaded = False self.marker_info = {} self.map_status = {} self.dropped_images = [] self.setChildrenCollapsible(False) left_side = QtWidgets.QWidget() self.addWidget(left_side) left_side.setLayout(QtWidgets.QFormLayout()) left_side.layout().setContentsMargins(0, 0, 0, 0) left_side.layout().setFieldGrowthPolicy( QtWidgets.QFormLayout.AllNonFixedFieldsGrow) # map # create handler for calls from JavaScript self.call_handler = CallHandler(parent=self) self.map = MapWebView(self.call_handler) self.map.drop_text.connect(self.drop_text) self.map.setAcceptDrops(False) self.addWidget(self.map) # search search_layout = QtWidgets.QFormLayout() search_layout.setContentsMargins(0, 0, 0, 0) search_layout.setVerticalSpacing(0) search_layout.setFieldGrowthPolicy( QtWidgets.QFormLayout.AllNonFixedFieldsGrow) self.edit_box = ComboBox() self.edit_box.setEditable(True) self.edit_box.setInsertPolicy(QtWidgets.QComboBox.NoInsert) self.edit_box.lineEdit().setPlaceholderText( translate('PhotiniMap', '<new search>')) self.edit_box.lineEdit().returnPressed.connect(self.search) self.edit_box.activated.connect(self.goto_search_result) self.clear_search() self.edit_box.setEnabled(False) search_layout.addRow(translate('PhotiniMap', 'Search'), self.edit_box) # search terms and conditions terms = self.search_terms() if terms: search_layout.addRow(*terms) left_side.layout().addRow(search_layout) if terms: divider = QtWidgets.QFrame() divider.setFrameStyle(QtWidgets.QFrame.HLine) left_side.layout().addRow(divider) left_side.layout().addItem( QtWidgets.QSpacerItem(1, 1000, vPolicy=QtWidgets.QSizePolicy.Expanding)) # latitude & longitude layout = QtWidgets.QHBoxLayout() self.coords = SingleLineEdit() self.coords.editingFinished.connect(self.new_coords) self.coords.setEnabled(False) layout.addWidget(self.coords) # convert lat/lng to location info self.auto_location = QtWidgets.QPushButton( translate('PhotiniMap', six.unichr(0x21e8) + ' address')) self.auto_location.setFixedHeight(self.coords.height()) self.auto_location.setEnabled(False) self.auto_location.clicked.connect(self.get_address) layout.addWidget(self.auto_location) left_side.layout().addRow(translate('PhotiniMap', 'Lat, long'), layout) # location info self.location_widgets = [] self.location_info = QtWidgets.QTabWidget() tab_bar = QTabBar() self.location_info.setTabBar(tab_bar) tab_bar.context_menu.connect(self.location_tab_context_menu) tab_bar.tabMoved.connect(self.location_tab_moved) self.location_info.setElideMode(Qt.ElideLeft) self.location_info.setMovable(True) self.location_info.setEnabled(False) left_side.layout().addRow(self.location_info) # address lookup (and default search) terms and conditions layout = QtWidgets.QHBoxLayout() if terms: widget = CompactButton( self.tr('Address lookup\npowered by OpenCage')) else: widget = CompactButton( self.tr('Search && lookup\npowered by OpenCage')) widget.clicked.connect(self.load_tou_opencage) layout.addWidget(widget) widget = CompactButton( self.tr('Geodata © OpenStreetMap\ncontributors')) widget.clicked.connect(self.load_tou_osm) layout.addWidget(widget) left_side.layout().addRow(layout) # other init self.image_list.image_list_changed.connect(self.image_list_changed) self.splitterMoved.connect(self.new_split) self.block_timer = QtCore.QTimer(self) self.block_timer.setInterval(5000) self.block_timer.setSingleShot(True) self.block_timer.timeout.connect(self.enable_search)
def __init__(self, image_list, parent=None): super(PhotiniMap, self).__init__(parent) self.logger = logging.getLogger(self.__class__.__name__) self.app = QtWidgets.QApplication.instance() self.config_store = self.app.config_store self.image_list = image_list self.multiple_values = multiple_values() self.script_dir = pkg_resources.resource_filename( 'photini', 'data/' + self.__class__.__name__.lower() + '/') self.drag_icon = QtGui.QPixmap( os.path.join(self.script_dir, 'grey_marker.png')) self.location = {} self.search_string = None self.map_loaded = False self.marker_images = {} layout = QtWidgets.QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setRowStretch(6, 1) layout.setColumnStretch(1, 1) self.setLayout(layout) # map self.map = WebView() self.map.setPage(WebPage(parent=self.map)) self.map.setAcceptDrops(False) self.map.page().setLinkDelegationPolicy( QtWebKitWidgets.QWebPage.DelegateAllLinks) self.map.page().linkClicked.connect(self.link_clicked) self.map.page().loadFinished.connect(self.load_finished) self.map.page().mainFrame().javaScriptWindowObjectCleared.connect( self.java_script_window_object_cleared) self.map.drop_text.connect(self.drop_text) self.layout().addWidget(self.map, 0, 1, 8, 1) # search self.layout().addWidget( QtWidgets.QLabel(translate('PhotiniMap', 'Search:')), 0, 0) self.edit_box = QtWidgets.QComboBox() self.edit_box.setMinimumWidth(200) self.edit_box.setEditable(True) self.edit_box.setInsertPolicy(QtWidgets.QComboBox.NoInsert) self.edit_box.lineEdit().setPlaceholderText( translate('PhotiniMap', '<new search>')) self.edit_box.lineEdit().returnPressed.connect(self.search) self.edit_box.activated.connect(self.goto_search_result) self.clear_search() self.edit_box.setEnabled(False) self.layout().addWidget(self.edit_box, 1, 0) # latitude & longitude self.layout().addWidget( QtWidgets.QLabel(translate('PhotiniMap', 'Latitude, longitude:')), 2, 0) self.coords = QtWidgets.QLineEdit() self.coords.editingFinished.connect(self.new_coords) self.coords.setEnabled(False) self.layout().addWidget(self.coords, 3, 0) # load map button self.load_map = QtWidgets.QPushButton( translate('PhotiniMap', '\nLoad map\n')) self.load_map.clicked.connect(self.initialise) self.layout().addWidget(self.load_map, 7, 0) # other init self.image_list.image_list_changed.connect(self.image_list_changed)
def diff_selected_metadata(self): dialog = QtWidgets.QDialog(parent=self) dialog.setLayout(QtWidgets.QVBoxLayout()) dialog.setFixedSize(min(800, self.window().width()), min(400, self.window().height())) table = QtWidgets.QTableWidget() table.setColumnCount(3) table.setHorizontalHeaderLabels([ translate('ImageList', 'new value'), translate('ImageList', 'undo'), translate('ImageList', 'old value') ]) table.horizontalHeader().setSectionResizeMode( 0, QtWidgets.QHeaderView.Stretch) table.horizontalHeader().setSectionResizeMode( 2, QtWidgets.QHeaderView.Stretch) dialog.layout().addWidget(table) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addWidget(button_box) changed = False position = None for image in self.get_selected_images(): if not image.metadata.changed(): continue dialog.setWindowTitle( translate('ImageList', 'Metadata differences: {}').format(image.name)) labels = [] row = 0 undo = {} table.clearContents() new_md = image.metadata old_md = Metadata(image.path) for key in ('title', 'description', 'keywords', 'rating', 'copyright', 'creator', 'date_taken', 'date_digitised', 'date_modified', 'orientation', 'camera_model', 'lens_model', 'lens_spec', 'focal_length', 'focal_length_35', 'aperture', 'latlong', 'altitude', 'location_taken', 'location_shown', 'thumbnail'): values = getattr(new_md, key), getattr(old_md, key) if values[0] == values[1]: continue values = [str(x or '') for x in values] table.setRowCount(row + 1) for n, value in enumerate(values): item = QtWidgets.QTableWidgetItem(value) table.setItem(row, n * 2, item) undo[key] = QtWidgets.QTableWidgetItem() undo[key].setFlags(undo[key].flags() | Qt.ItemIsUserCheckable) undo[key].setCheckState(Qt.Unchecked) table.setItem(row, 1, undo[key]) labels.append(key) row += 1 if not row: continue table.setVerticalHeaderLabels(labels) table.resizeColumnsToContents() table.resizeRowsToContents() if position: dialog.move(position) if dialog.exec_() != QtWidgets.QDialog.Accepted: return position = dialog.pos() undo_all = True for key, widget in undo.items(): if widget.checkState() == Qt.Checked: setattr(new_md, key, getattr(old_md, key)) changed = True else: undo_all = False if undo_all: image.reload_metadata() if changed: self.emit_selection()
def contextMenuEvent(self, event): menu = QtWidgets.QMenu(self) self.image_list.add_selected_actions(menu) menu.exec_(event.globalPos())
def data_form(self): widgets = {} scrollarea = QtWidgets.QScrollArea() scrollarea.setFrameStyle(QtWidgets.QFrame.NoFrame) scrollarea.setWidgetResizable(True) form = QtWidgets.QWidget() form.setLayout(QtWidgets.QFormLayout()) # creator widgets['creator'] = SingleLineEdit( length_check=ImageMetadata.max_bytes('creator'), spell_check=True, multi_string=True) widgets['creator'].setToolTip( translate('OwnerTab', 'Enter the name of the person that created this image.')) form.layout().addRow(translate('OwnerTab', 'Creator'), widgets['creator']) # creator title widgets['creator_title'] = SingleLineEdit( length_check=ImageMetadata.max_bytes('creator_title'), spell_check=True, multi_string=True) widgets['creator_title'].setToolTip( translate( 'OwnerTab', 'Enter the job title of the person listed in the Creator field.' )) form.layout().addRow(translate('OwnerTab', "Creator's Jobtitle"), widgets['creator_title']) # credit line widgets['credit_line'] = SingleLineEdit( length_check=ImageMetadata.max_bytes('credit_line'), spell_check=True) widgets['credit_line'].setToolTip( translate( 'OwnerTab', 'Enter who should be credited when this image is published.')) form.layout().addRow(translate('OwnerTab', 'Credit Line'), widgets['credit_line']) # copyright widgets['copyright'] = SingleLineEdit( length_check=ImageMetadata.max_bytes('copyright'), spell_check=True) widgets['copyright'].setToolTip( translate( 'OwnerTab', 'Enter a notice on the current owner of the' ' copyright for this image, such as "©2008 Jane Doe".')) form.layout().addRow(translate('OwnerTab', 'Copyright Notice'), widgets['copyright']) # usage terms widgets['usageterms'] = SingleLineEdit( length_check=ImageMetadata.max_bytes('usageterms'), spell_check=True) widgets['usageterms'].setToolTip( translate( 'OwnerTab', 'Enter instructions on how this image can legally be used.')) form.layout().addRow(translate('OwnerTab', 'Rights Usage Terms'), widgets['usageterms']) # special instructions widgets['instructions'] = SingleLineEdit( length_check=ImageMetadata.max_bytes('instructions'), spell_check=True) widgets['instructions'].setToolTip( translate( 'OwnerTab', 'Enter information about embargoes, or other' ' restrictions not covered by the Rights Usage Terms field.')) form.layout().addRow(translate('OwnerTab', 'Instructions'), widgets['instructions']) ## contact information contact_group = QtWidgets.QGroupBox() contact_group.setLayout(QtWidgets.QFormLayout()) # email addresses widgets['CiEmailWork'] = SingleLineEdit() widgets['CiEmailWork'].setToolTip( translate( 'OwnerTab', 'Enter the work email address(es) for the person' ' that created this image, such as [email protected].')) contact_group.layout().addRow(translate('OwnerTab', 'Email(s)'), widgets['CiEmailWork']) # URLs widgets['CiUrlWork'] = SingleLineEdit() widgets['CiUrlWork'].setToolTip( translate( 'OwnerTab', 'Enter the work Web URL(s) for the person' ' that created this image, such as http://www.domain.com/.')) contact_group.layout().addRow(translate('OwnerTab', 'Web URL(s)'), widgets['CiUrlWork']) # phone numbers widgets['CiTelWork'] = SingleLineEdit() widgets['CiTelWork'].setToolTip( translate( 'OwnerTab', 'Enter the work phone number(s) for the person' ' that created this image, using the international format,' ' such as +1 (123) 456789.')) contact_group.layout().addRow(translate('OwnerTab', 'Phone(s)'), widgets['CiTelWork']) # address widgets['CiAdrExtadr'] = MultiLineEdit( length_check=ImageMetadata.max_bytes('contact_info'), spell_check=True) widgets['CiAdrExtadr'].setToolTip( translate('OwnerTab', 'Enter address for the person that created this image.')) contact_group.layout().addRow(translate('OwnerTab', 'Address'), widgets['CiAdrExtadr']) # city widgets['CiAdrCity'] = SingleLineEdit(spell_check=True) widgets['CiAdrCity'].setToolTip( translate( 'OwnerTab', 'Enter the city for the address of the person' ' that created this image.')) contact_group.layout().addRow(translate('OwnerTab', 'City'), widgets['CiAdrCity']) # postcode widgets['CiAdrPcode'] = SingleLineEdit() widgets['CiAdrPcode'].setToolTip( translate( 'OwnerTab', 'Enter the postal code for the address of the person' ' that created this image.')) contact_group.layout().addRow(translate('OwnerTab', 'Postal Code'), widgets['CiAdrPcode']) # region widgets['CiAdrRegion'] = SingleLineEdit(spell_check=True) widgets['CiAdrRegion'].setToolTip( translate( 'OwnerTab', 'Enter the state for the address of the person' ' that created this image.')) contact_group.layout().addRow(translate('OwnerTab', 'State/Province'), widgets['CiAdrRegion']) # country widgets['CiAdrCtry'] = SingleLineEdit(spell_check=True) widgets['CiAdrCtry'].setToolTip( translate( 'OwnerTab', 'Enter the country name for the address of the person' ' that created this image.')) contact_group.layout().addRow(translate('OwnerTab', 'Country'), widgets['CiAdrCtry']) form.layout().addRow(translate('OwnerTab', 'Contact Information'), contact_group) scrollarea.setWidget(form) return scrollarea, widgets
def __init__(self, *arg, **kw): super(PicasaUploadConfig, self).__init__(*arg, **kw) self.setLayout(QtWidgets.QHBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.widgets = {} ## album details, left hand side album_group = QtWidgets.QGroupBox(self.tr('Collection / Album')) album_group.setLayout(QtWidgets.QHBoxLayout()) album_form_left = QtWidgets.QFormLayout() album_form_left.setFieldGrowthPolicy( QtWidgets.QFormLayout.AllNonFixedFieldsGrow) album_group.layout().addLayout(album_form_left) # album title / selector self.albums = QtWidgets.QComboBox() self.albums.setEditable(True) self.albums.setInsertPolicy(QtWidgets.QComboBox.NoInsert) self.albums.activated.connect(self.switch_album) self.albums.lineEdit().editingFinished.connect(self.new_title) album_form_left.addRow(self.tr('Title'), self.albums) # album description self.widgets['description'] = MultiLineEdit(spell_check=True) self.widgets['description'].editingFinished.connect( self.new_album_details) album_form_left.addRow(self.tr('Description'), self.widgets['description']) # album location self.widgets['location'] = QtWidgets.QLineEdit() self.widgets['location'].editingFinished.connect( self.new_album_details) album_form_left.addRow(self.tr('Place taken'), self.widgets['location']) # album visibility self.widgets['access'] = QtWidgets.QComboBox() self.widgets['access'].addItem(self.tr('Public on the web'), 'public') self.widgets['access'].addItem( self.tr('Limited, anyone with the link'), 'private') self.widgets['access'].addItem(self.tr('Only you'), 'protected') self.widgets['access'].currentIndexChanged.connect(self.new_access) album_form_left.addRow(self.tr('Visibility'), self.widgets['access']) ## album buttons buttons = QtWidgets.QHBoxLayout() buttons.addStretch(stretch=60) album_form_left.addRow(buttons) # new album new_album_button = QtWidgets.QPushButton(self.tr('New album')) new_album_button.clicked.connect(self.new_album) buttons.addWidget(new_album_button, stretch=20) # delete album delete_album_button = QtWidgets.QPushButton(self.tr('Delete album')) delete_album_button.clicked.connect(self.delete_album) buttons.addWidget(delete_album_button, stretch=20) ## album details, right hand side album_form_right = QtWidgets.QFormLayout() album_form_right.setFieldGrowthPolicy( QtWidgets.QFormLayout.AllNonFixedFieldsGrow) album_group.layout().addLayout(album_form_right) # album date self.widgets['timestamp'] = QtWidgets.QDateEdit() self.widgets['timestamp'].setMinimumDateTime( QtCore.QDateTime.fromTime_t(0)) self.widgets['timestamp'].setCalendarPopup(True) self.widgets['timestamp'].editingFinished.connect( self.new_album_details) album_form_right.addRow(self.tr('Date'), self.widgets['timestamp']) # album thumbnail self.album_thumb = QtWidgets.QLabel() self.album_thumb.setFixedWidth(160) album_form_right.addRow(self.album_thumb) self.layout().addWidget(album_group)
def search_terms(self): widget = QtWidgets.QLabel(self.tr('Search powered by Google')) scale_font(widget, 80) return '', widget
def __init__(self, image_list, parent=None): super(TabWidget, self).__init__(parent) app = QtWidgets.QApplication.instance() app.aboutToQuit.connect(self.shutdown) if gp and app.test_mode: self.gp_log = gp.check_result(gp.use_python_logging()) self.config_store = app.config_store self.image_list = image_list self.setLayout(QtWidgets.QGridLayout()) form = QtWidgets.QFormLayout() form.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow) self.nm = NameMangler() self.file_data = {} self.file_list = [] self.source = None self.file_reader = None self.file_writer = None # source selector box = QtWidgets.QHBoxLayout() box.setContentsMargins(0, 0, 0, 0) self.source_selector = QtWidgets.QComboBox() self.source_selector.currentIndexChanged.connect(self.new_source) box.addWidget(self.source_selector) refresh_button = QtWidgets.QPushButton(self.tr('refresh')) refresh_button.clicked.connect(self.refresh) box.addWidget(refresh_button) box.setStretch(0, 1) form.addRow(self.tr('Source'), box) # path format self.path_format = QtWidgets.QLineEdit() self.path_format.setValidator(PathFormatValidator()) self.path_format.textChanged.connect(self.nm.new_format) self.path_format.editingFinished.connect(self.path_format_finished) form.addRow(self.tr('Target format'), self.path_format) # path example self.path_example = QtWidgets.QLabel() self.nm.new_example.connect(self.path_example.setText) form.addRow('=>', self.path_example) self.layout().addLayout(form, 0, 0) # file list self.file_list_widget = QtWidgets.QListWidget() self.file_list_widget.setSelectionMode( QtWidgets.QAbstractItemView.ExtendedSelection) self.file_list_widget.itemSelectionChanged.connect(self.selection_changed) self.layout().addWidget(self.file_list_widget, 1, 0) # selection buttons buttons = QtWidgets.QVBoxLayout() buttons.addStretch(1) self.selected_count = QtWidgets.QLabel() self.selection_changed() buttons.addWidget(self.selected_count) select_all = QtWidgets.QPushButton(self.tr('Select\nall')) select_all.clicked.connect(self.select_all) buttons.addWidget(select_all) select_new = QtWidgets.QPushButton(self.tr('Select\nnew')) select_new.clicked.connect(self.select_new) buttons.addWidget(select_new) self.copy_button = StartStopButton(self.tr('Copy\nphotos'), self.tr('Stop\nimport')) self.copy_button.click_start.connect(self.copy_selected) self.copy_button.click_stop.connect(self.stop_copy) buttons.addWidget(self.copy_button) self.layout().addLayout(buttons, 0, 1, 2, 1) # final initialisation self.image_list.sort_order_changed.connect(self.sort_file_list) path = os.path.expanduser('~/Pictures') if not os.path.isdir(path) and sys.platform == 'win32': try: import win32com.shell as ws path = ws.shell.SHGetFolderPath( 0, ws.shellcon.CSIDL_MYPICTURES, None, 0) except ImportError: pass self.path_format.setText( os.path.join(path, '%Y', '%Y_%m_%d', '{name}')) self.refresh() self.list_files()
def __init__(self, images, *arg, **kw): super(NewLensDialog, self).__init__(*arg, **kw) self.setWindowTitle(self.tr('Photini: define lens')) self.setLayout(QtWidgets.QVBoxLayout()) # main dialog area scroll_area = QtWidgets.QScrollArea() scroll_area.setWidgetResizable(True) self.layout().addWidget(scroll_area) panel = QtWidgets.QWidget() panel.setLayout(QtWidgets.QFormLayout()) # ok & cancel buttons button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) self.layout().addWidget(button_box) # model self.lens_model = QtWidgets.QLineEdit() self.lens_model.setMinimumWidth(250) panel.layout().addRow(self.tr('Model name'), self.lens_model) # maker self.lens_make = QtWidgets.QLineEdit() panel.layout().addRow(self.tr("Maker's name"), self.lens_make) # serial number self.lens_serial = QtWidgets.QLineEdit() panel.layout().addRow(self.tr('Serial number'), self.lens_serial) ## spec has four items self.lens_spec = {} # min focal length self.lens_spec['min_fl'] = QtWidgets.QLineEdit() self.lens_spec['min_fl'].setValidator( QtGui.QDoubleValidator(bottom=0.0)) panel.layout().addRow(self.tr('Minimum focal length (mm)'), self.lens_spec['min_fl']) # min focal length aperture self.lens_spec['min_fl_fn'] = QtWidgets.QLineEdit() self.lens_spec['min_fl_fn'].setValidator(DoubleValidator(bottom=0.0)) panel.layout().addRow(self.tr('Aperture at min. focal length f/'), self.lens_spec['min_fl_fn']) # max focal length self.lens_spec['max_fl'] = QtWidgets.QLineEdit() self.lens_spec['max_fl'].setValidator( QtGui.QDoubleValidator(bottom=0.0)) panel.layout().addRow(self.tr('Maximum focal length (mm)'), self.lens_spec['max_fl']) # max focal length aperture self.lens_spec['max_fl_fn'] = QtWidgets.QLineEdit() self.lens_spec['max_fl_fn'].setValidator(DoubleValidator(bottom=0.0)) panel.layout().addRow(self.tr('Aperture at max. focal length f/'), self.lens_spec['max_fl_fn']) # add panel to scroll area after its size is known scroll_area.setWidget(panel) # fill in any values we can from existing metadata for image in images: if image.metadata.lens_model: self.lens_model.setText(image.metadata.lens_model.value) if image.metadata.lens_make: self.lens_make.setText(image.metadata.lens_make.value) if image.metadata.lens_serial: self.lens_serial.setText(image.metadata.lens_serial.value) spec = image.metadata.lens_spec for key in self.lens_spec: if spec and spec.value[key]: self.lens_spec[key].setText('{:g}'.format( float(spec.value[key])))
def __init__(self, image_list, *arg, **kw): super(Technical, self).__init__(*arg, **kw) self.config_store = QtWidgets.QApplication.instance().config_store self.image_list = image_list self.setLayout(QtWidgets.QHBoxLayout()) self.widgets = {} self.date_widget = {} self.link_widget = {} # store lens data in another object self.lens_data = LensData() # date and time date_group = QtWidgets.QGroupBox(self.tr('Date and time')) date_group.setLayout(QtWidgets.QFormLayout()) # create date and link widgets for master in self._master_slave: self.date_widget[master] = DateAndTimeWidget(master) self.date_widget[master].new_value.connect(self.new_date_value) slave = self._master_slave[master] if slave: self.link_widget[master, slave] = DateLink(master) self.link_widget[master, slave].new_link.connect(self.new_link) self.link_widget['taken', 'digitised'].setText( self.tr("Link 'taken' and 'digitised'")) self.link_widget['digitised', 'modified'].setText( self.tr("Link 'digitised' and 'modified'")) # add to layout date_group.layout().addRow(self.tr('Taken'), self.date_widget['taken']) date_group.layout().addRow('', self.link_widget['taken', 'digitised']) date_group.layout().addRow(self.tr('Digitised'), self.date_widget['digitised']) date_group.layout().addRow('', self.link_widget['digitised', 'modified']) date_group.layout().addRow(self.tr('Modified'), self.date_widget['modified']) # offset self.offset_widget = OffsetWidget() self.offset_widget.apply_offset.connect(self.apply_offset) date_group.layout().addRow(self.tr('Adjust times'), self.offset_widget) self.layout().addWidget(date_group) # other other_group = QtWidgets.QGroupBox(self.tr('Other')) other_group.setLayout(QtWidgets.QFormLayout()) other_group.layout().setFieldGrowthPolicy( QtWidgets.QFormLayout.AllNonFixedFieldsGrow) # orientation self.widgets['orientation'] = DropdownEdit() self.widgets['orientation'].add_item(self.tr('normal'), 1) self.widgets['orientation'].add_item(self.tr('rotate -90'), 6) self.widgets['orientation'].add_item(self.tr('rotate +90'), 8) self.widgets['orientation'].add_item(self.tr('rotate 180'), 3) self.widgets['orientation'].add_item(self.tr('reflect left-right'), 2) self.widgets['orientation'].add_item(self.tr('reflect top-bottom'), 4) self.widgets['orientation'].add_item(self.tr('reflect tr-bl'), 5) self.widgets['orientation'].add_item(self.tr('reflect tl-br'), 7) self.widgets['orientation'].new_value.connect(self.new_orientation) other_group.layout().addRow( self.tr('Orientation'), self.widgets['orientation']) # lens model self.widgets['lens_model'] = DropdownEdit() self.widgets['lens_model'].setMinimumWidth( self.widgets['lens_model'].fontMetrics().width('x' * 30)) self.widgets['lens_model'].setContextMenuPolicy(Qt.CustomContextMenu) self.widgets['lens_model'].add_item( self.tr('<define new lens>'), '<add lens>') for model in self.lens_data.lenses: self.widgets['lens_model'].add_item(model, model) self.widgets['lens_model'].new_value.connect(self.new_lens_model) self.widgets['lens_model'].customContextMenuRequested.connect( self.remove_lens_model) other_group.layout().addRow( self.tr('Lens model'), self.widgets['lens_model']) # lens specification self.widgets['lens_spec'] = LensSpecWidget() other_group.layout().addRow( self.tr('Lens details'), self.widgets['lens_spec']) # focal length self.widgets['focal_length'] = NumberEdit() self.widgets['focal_length'].setValidator(DoubleValidator()) self.widgets['focal_length'].validator().setBottom(0.1) self.widgets['focal_length'].new_value.connect(self.new_focal_length) other_group.layout().addRow( self.tr('Focal length (mm)'), self.widgets['focal_length']) # 35mm equivalent focal length self.widgets['focal_length_35'] = NumberEdit() self.widgets['focal_length_35'].setValidator(IntValidator()) self.widgets['focal_length_35'].validator().setBottom(1) self.widgets['focal_length_35'].new_value.connect(self.new_focal_length_35) other_group.layout().addRow( self.tr('35mm equiv (mm)'), self.widgets['focal_length_35']) # aperture self.widgets['aperture'] = NumberEdit() self.widgets['aperture'].setValidator(DoubleValidator()) self.widgets['aperture'].validator().setBottom(0.1) self.widgets['aperture'].new_value.connect(self.new_aperture) other_group.layout().addRow( self.tr('Aperture f/'), self.widgets['aperture']) self.layout().addWidget(other_group, stretch=1) # disable until an image is selected self.setEnabled(False)
def __init__(self, *arg, **kw): super(FlickrUploadConfig, self).__init__(*arg, **kw) self.setLayout(QtWidgets.QGridLayout()) self.layout().setContentsMargins(0, 0, 0, 0) # privacy settings self.privacy = {} privacy_group = QtWidgets.QGroupBox( translate('FlickrTab', 'Who can see the photos?')) privacy_group.setLayout(QtWidgets.QVBoxLayout()) self.privacy['private'] = QtWidgets.QRadioButton( translate('FlickrTab', 'Only you')) privacy_group.layout().addWidget(self.privacy['private']) ff_group = QtWidgets.QGroupBox() ff_group.setFlat(True) ff_group.setLayout(QtWidgets.QVBoxLayout()) ff_group.layout().setContentsMargins(10, 0, 0, 0) self.privacy['friends'] = QtWidgets.QCheckBox( translate('FlickrTab', 'Your friends')) ff_group.layout().addWidget(self.privacy['friends']) self.privacy['family'] = QtWidgets.QCheckBox( translate('FlickrTab', 'Your family')) ff_group.layout().addWidget(self.privacy['family']) privacy_group.layout().addWidget(ff_group) self.privacy['public'] = QtWidgets.QRadioButton( translate('FlickrTab', 'Anyone')) self.privacy['public'].toggled.connect(self.enable_ff) self.privacy['public'].setChecked(True) privacy_group.layout().addWidget(self.privacy['public']) self.hidden = QtWidgets.QCheckBox( translate('FlickrTab', 'Hidden from search')) privacy_group.layout().addWidget(self.hidden) privacy_group.layout().addStretch(1) self.layout().addWidget(privacy_group, 0, 0, 3, 1) # content type self.content_type = {} content_group = QtWidgets.QGroupBox( translate('FlickrTab', 'Content type')) content_group.setLayout(QtWidgets.QVBoxLayout()) self.content_type['photo'] = QtWidgets.QRadioButton( translate('FlickrTab', 'Photo')) self.content_type['photo'].setChecked(True) content_group.layout().addWidget(self.content_type['photo']) self.content_type['screenshot'] = QtWidgets.QRadioButton( translate('FlickrTab', 'Screenshot')) content_group.layout().addWidget(self.content_type['screenshot']) self.content_type['other'] = QtWidgets.QRadioButton( translate('FlickrTab', 'Art/Illustration')) content_group.layout().addWidget(self.content_type['other']) content_group.layout().addStretch(1) self.layout().addWidget(content_group, 0, 1) # synchronise metadata self.sync_button = QtWidgets.QPushButton( translate('FlickrTab', 'Synchronise')) self.sync_button.clicked.connect(self.sync_metadata) self.layout().addWidget(self.sync_button, 1, 1) # create new set new_set_button = QtWidgets.QPushButton( translate('FlickrTab', 'New album')) new_set_button.clicked.connect(self.new_set) self.layout().addWidget(new_set_button, 2, 1) # list of sets widget sets_group = QtWidgets.QGroupBox( translate('FlickrTab', 'Add to albums')) sets_group.setLayout(QtWidgets.QVBoxLayout()) scrollarea = QtWidgets.QScrollArea() scrollarea.setFrameStyle(QtWidgets.QFrame.NoFrame) scrollarea.setStyleSheet("QScrollArea { background-color: transparent }") self.sets_widget = QtWidgets.QWidget() self.sets_widget.setLayout(QtWidgets.QVBoxLayout()) self.sets_widget.layout().setSpacing(0) self.sets_widget.layout().setSizeConstraint( QtWidgets.QLayout.SetMinAndMaxSize) scrollarea.setWidget(self.sets_widget) self.sets_widget.setAutoFillBackground(False) sets_group.layout().addWidget(scrollarea) self.layout().addWidget(sets_group, 0, 2, 3, 1) self.layout().setColumnStretch(2, 1)
def __init__(self, image_list, parent=None): super(PhotiniMap, self).__init__(parent) self.app = QtWidgets.QApplication.instance() self.image_list = image_list name = self.__module__.split('.')[-1] self.script_dir = pkg_resources.resource_filename( 'photini', 'data/' + name + '/') self.drag_icon = QtGui.QPixmap( os.path.join(self.script_dir, '../map_pin_grey.png')) self.drag_hotspot = 11, 35 self.search_string = None self.map_loaded = 0 # not loaded self.marker_info = {} self.map_status = {} self.dropped_images = [] self.geocoder = self.get_geocoder() self.gpx_ids = [] self.widgets = {} self.setLayout(QtWidgets.QHBoxLayout()) ## left side left_side = QtWidgets.QGridLayout() # latitude & longitude self.widgets['latlon'] = LatLongDisplay(self.image_list) left_side.addWidget(self.widgets['latlon'].label, 0, 0) self.widgets['latlon'].changed.connect(self.new_coords) left_side.addWidget(self.widgets['latlon'], 0, 1) # altitude label = QtWidgets.QLabel(translate('MapTabsAll', 'Altitude')) label.setAlignment(Qt.AlignRight) left_side.addWidget(label, 1, 0) self.widgets['altitude'] = DoubleSpinBox() self.widgets['altitude'].setSuffix(' m') self.widgets['altitude'].new_value.connect(self.new_altitude) left_side.addWidget(self.widgets['altitude'], 1, 1) if hasattr(self.geocoder, 'get_altitude'): self.widgets['get_altitude'] = QtWidgets.QPushButton( translate('MapTabsAll', 'Get altitude from map')) self.widgets['get_altitude'].clicked.connect(self.get_altitude) left_side.addWidget(self.widgets['get_altitude'], 2, 1) else: self.widgets['get_altitude'] = None # search label = QtWidgets.QLabel(translate('MapTabsAll', 'Search')) label.setAlignment(Qt.AlignRight) left_side.addWidget(label, 3, 0) self.widgets['search'] = ComboBox() self.widgets['search'].setEditable(True) self.widgets['search'].setInsertPolicy(QtWidgets.QComboBox.NoInsert) self.widgets['search'].lineEdit().setPlaceholderText( translate('MapTabsAll', '<new search>')) self.widgets['search'].lineEdit().returnPressed.connect(self.search) self.widgets['search'].activated.connect(self.goto_search_result) self.clear_search() self.widgets['search'].setEnabled(False) left_side.addWidget(self.widgets['search'], 3, 1) # search terms and conditions for n, widget in enumerate(self.geocoder.search_terms()): left_side.addWidget(widget, n+4, 0, 1, 2) left_side.setColumnStretch(1, 1) left_side.setRowStretch(7, 1) # GPX importer if self.app.gpx_importer: button = QtWidgets.QPushButton( translate('MapTabsAll', 'Load GPX file')) button.clicked.connect(self.load_gpx) left_side.addWidget(button, 8, 1) self.widgets['set_from_gpx'] = QtWidgets.QPushButton( translate('MapTabsAll', 'Set coords from GPX')) self.widgets['set_from_gpx'].setEnabled(False) self.widgets['set_from_gpx'].clicked.connect(self.set_from_gpx) left_side.addWidget(self.widgets['set_from_gpx'], 9, 1) self.widgets['clear_gpx'] = QtWidgets.QPushButton( translate('MapTabsAll', 'Remove GPX data')) self.widgets['clear_gpx'].setEnabled(False) self.widgets['clear_gpx'].clicked.connect(self.clear_gpx) left_side.addWidget(self.widgets['clear_gpx'], 10, 1) self.layout().addLayout(left_side) # map # create handler for calls from JavaScript self.call_handler = CallHandler(parent=self) self.widgets['map'] = MapWebView(self.call_handler) self.widgets['map'].drop_text.connect(self.drop_text) self.widgets['map'].setAcceptDrops(False) self.layout().addWidget(self.widgets['map']) self.layout().setStretch(1, 1) # other init self.image_list.image_list_changed.connect(self.image_list_changed)
def _find_local(self, photo, unknowns): granularity = int(photo['datetakengranularity']) min_taken_date = datetime.strptime( photo['datetaken'], '%Y-%m-%d %H:%M:%S') if granularity <= 0: max_taken_date = min_taken_date + timedelta(seconds=1) elif granularity <= 4: max_taken_date = min_taken_date + timedelta(days=31) else: max_taken_date = min_taken_date + timedelta(days=366) candidates = [] for candidate in unknowns: if not candidate.metadata.date_taken: continue date_taken = candidate.metadata.date_taken['datetime'] if date_taken < min_taken_date or date_taken > max_taken_date: continue candidates.append(candidate) if not candidates: return None rsp = requests.get(photo['url_t']) if rsp.status_code == 200: flickr_icon = rsp.content else: logger.error('HTTP error %d (%s)', rsp.status_code, photo['url_t']) return None # get user to choose matching image file dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(translate('FlickrTab', 'Select an image')) dialog.setLayout(QtWidgets.QFormLayout()) dialog.layout().setFieldGrowthPolicy( QtWidgets.QFormLayout.AllNonFixedFieldsGrow) pixmap = QtGui.QPixmap() pixmap.loadFromData(flickr_icon) label = QtWidgets.QLabel() label.setPixmap(pixmap) dialog.layout().addRow(label, QtWidgets.QLabel(translate( 'FlickrTab', 'Which image file matches\nthis picture on Flickr?'))) divider = QtWidgets.QFrame() divider.setFrameStyle(QtWidgets.QFrame.HLine) dialog.layout().addRow(divider) buttons = {} for candidate in candidates: label = QtWidgets.QLabel() pixmap = candidate.image.pixmap() if pixmap: label.setPixmap(pixmap) else: label.setText(candidate.image.text()) button = QtWidgets.QPushButton( os.path.basename(candidate.path)) button.setToolTip(candidate.path) button.setCheckable(True) button.clicked.connect(dialog.accept) dialog.layout().addRow(label, button) buttons[button] = candidate button = QtWidgets.QPushButton(translate('FlickrTab', 'No match')) button.setDefault(True) button.clicked.connect(dialog.reject) dialog.layout().addRow('', button) if dialog.exec_() == QtWidgets.QDialog.Accepted: for button, candidate in buttons.items(): if button.isChecked(): return candidate return None
def do_import(self, parent): args = [ parent, self.tr('Import GPX file'), parent.app.config_store.get('paths', 'gpx', ''), self.tr("GPX files (*.gpx *.GPX *.Gpx);;All files (*)") ] if eval(parent.app.config_store.get('pyqt', 'native_dialog', 'True')): pass else: args += [None, QtWidgets.QFileDialog.DontUseNativeDialog] path = QtWidgets.QFileDialog.getOpenFileName(*args) path = path[0] if not path: return parent.app.config_store.set('paths', 'gpx', os.path.dirname(os.path.abspath(path))) # get user options config_store = QtWidgets.QApplication.instance().config_store dialog = QtWidgets.QDialog(parent=parent) dialog.setWindowTitle(self.tr('GPX options')) dialog.setLayout(QtWidgets.QFormLayout()) max_interval = QtWidgets.QSpinBox() max_interval.setRange(60, 300) max_interval.setValue( int(config_store.get('gpx_importer', 'interval', '120'))) max_interval.setSuffix(self.tr(' secs')) dialog.layout().addRow(self.tr('Max time between points'), max_interval) max_dilution = QtWidgets.QDoubleSpinBox() max_dilution.setRange(1.0, 100.0) max_dilution.setValue( float(config_store.get('gpx_importer', 'dilution', '2.5'))) max_dilution.setSingleStep(0.1) dialog.layout().addRow(self.tr('Max dilution of precision'), max_dilution) if hasattr(parent.tabs.currentWidget(), 'plot_track'): plot_track = QtWidgets.QCheckBox() plot_track.setChecked( bool(config_store.get('gpx_importer', 'plot', 'True'))) dialog.layout().addRow(self.tr('Plot track on map'), plot_track) else: plot_track = False button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok) button_box.accepted.connect(dialog.accept) dialog.layout().addWidget(button_box) dialog.exec_() max_interval = max_interval.value() max_dilution = max_dilution.value() config_store.set('gpx_importer', 'interval', max_interval) config_store.set('gpx_importer', 'dilution', max_dilution) if plot_track: plot_track = plot_track.isChecked() config_store.set('gpx_importer', 'plot', plot_track) # make a list of all points in the file points = [] discards = 0 for p in self.read_file(path): if p.horizontal_dilution and p.horizontal_dilution > max_dilution: discards += 1 continue time_stamp = p.time if time_stamp.tzinfo is not None: # convert timestamp to UTC utc_offset = time_stamp.utcoffset() time_stamp = (time_stamp - utc_offset).replace(tzinfo=None) # add point to list points.append((time_stamp, p.latitude, p.longitude)) if discards: logger.warning('Discarded %d low accuracy points', discards) if not points: logger.warning('No points found in file "%s"', path) return logger.warning('Using %d points', len(points)) # sort points by timestamp points.sort(key=lambda x: x[0]) # display on map if plot_track: # divide points into contiguous tracks tracks = [] t = [] for p in points: if t and (p[0] - t[-1][0]).total_seconds() > max_interval: tracks.append(t) t = [] t.append(p) if t: tracks.append(t) parent.tabs.currentWidget().plot_track(tracks) # set image coordinates max_interval = max_interval / 2.0 for image in parent.image_list.get_selected_images(): if not image.metadata.date_taken: continue time_stamp = image.metadata.date_taken.to_utc() if len(points) < 2: lo, hi = 0, 0 elif time_stamp < points[0][0]: lo, hi = 0, 1 elif time_stamp > points[-1][0]: lo, hi = -2, -1 else: # binary search for points with nearest timestamps lo, hi = 0, len(points) - 1 while hi - lo > 1: mid = (lo + hi) // 2 if time_stamp >= points[mid][0]: lo = mid elif time_stamp <= points[mid][0]: hi = mid # use linear interpolation (or extrapolation) to set lat & long dt_lo = (time_stamp - points[lo][0]).total_seconds() dt_hi = (time_stamp - points[hi][0]).total_seconds() if abs(dt_lo) > max_interval and abs(dt_hi) > max_interval: logger.info('No point for time %s', image.metadata.date_taken) continue if dt_lo == dt_hi: beta = 0.5 else: beta = dt_lo / (dt_lo - dt_hi) lat = points[lo][1] + (beta * (points[hi][1] - points[lo][1])) lng = points[lo][2] + (beta * (points[hi][2] - points[lo][2])) image.metadata.latlong = lat, lng parent.image_list.emit_selection()
def __init__(self, parent): QtWidgets.QDialog.__init__(self, parent) self.config_store = QtWidgets.QApplication.instance().config_store self.setWindowTitle(self.tr('Photini: settings')) self.setLayout(QtWidgets.QVBoxLayout()) # main dialog area scroll_area = QtWidgets.QScrollArea() self.layout().addWidget(scroll_area) panel = QtWidgets.QWidget() panel.setLayout(QtWidgets.QFormLayout()) # apply & cancel buttons self.button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Apply | QtWidgets.QDialogButtonBox.Cancel) self.button_box.clicked.connect(self.button_clicked) self.layout().addWidget(self.button_box) # copyright holder name self.copyright_name = QtWidgets.QLineEdit() self.copyright_name.setText( self.config_store.get('user', 'copyright_name', '')) self.copyright_name.setMinimumWidth(200) panel.layout().addRow(self.tr('Copyright holder'), self.copyright_name) # creator name self.creator_name = QtWidgets.QLineEdit() self.creator_name.setText( self.config_store.get('user', 'creator_name', '')) panel.layout().addRow(self.tr('Creator'), self.creator_name) # reset flickr self.reset_flickr = QtWidgets.QCheckBox() panel.layout().addRow(self.tr('Disconnect from Flickr'), self.reset_flickr) if not keyring or keyring.get_password('photini', 'flickr') is None: self.reset_flickr.setDisabled(True) panel.layout().labelForField(self.reset_flickr).setDisabled(True) # reset picasa self.reset_picasa = QtWidgets.QCheckBox() panel.layout().addRow(self.tr('Disconnect from Google Photos'), self.reset_picasa) if not keyring or keyring.get_password('photini', 'picasa') is None: self.reset_picasa.setDisabled(True) panel.layout().labelForField(self.reset_picasa).setDisabled(True) # reset facebook self.reset_facebook = QtWidgets.QCheckBox() panel.layout().addRow(self.tr('Disconnect from Facebook'), self.reset_facebook) if not keyring or keyring.get_password('photini', 'facebook') is None: self.reset_facebook.setDisabled(True) panel.layout().labelForField(self.reset_facebook).setDisabled(True) # IPTC data force_iptc = eval(self.config_store.get('files', 'force_iptc', 'False')) self.write_iptc = QtWidgets.QCheckBox(self.tr('Write unconditionally')) self.write_iptc.setChecked(force_iptc) panel.layout().addRow(self.tr('IPTC metadata'), self.write_iptc) # sidecar files if_mode = eval(self.config_store.get('files', 'image', 'True')) sc_mode = self.config_store.get('files', 'sidecar', 'auto') if not if_mode: sc_mode = 'always' self.sc_always = QtWidgets.QRadioButton(self.tr('Always create')) self.sc_always.setChecked(sc_mode == 'always') panel.layout().addRow(self.tr('Sidecar files'), self.sc_always) self.sc_auto = QtWidgets.QRadioButton(self.tr('Create if necessary')) self.sc_auto.setChecked(sc_mode == 'auto') self.sc_auto.setEnabled(if_mode) panel.layout().addRow('', self.sc_auto) self.sc_delete = QtWidgets.QRadioButton( self.tr('Delete when possible')) self.sc_delete.setChecked(sc_mode == 'delete') self.sc_delete.setEnabled(if_mode) panel.layout().addRow('', self.sc_delete) # image file locking self.write_if = QtWidgets.QCheckBox(self.tr('(when possible)')) self.write_if.setChecked(if_mode) self.write_if.clicked.connect(self.new_write_if) panel.layout().addRow(self.tr('Write to image'), self.write_if) # add panel to scroll area after its size is known scroll_area.setWidget(panel)
def show_terms(self): # return widgets to display map terms and conditions load_tou = QtWidgets.QPushButton(self.tr('Terms of Use')) load_tou.clicked.connect(self.load_tou) yield load_tou
def main(argv=None): if argv: sys.argv = argv # let PyQt handle its options (need at least one argument after options) sys.argv.append('xxx') app = QtWidgets.QApplication(sys.argv) del sys.argv[-1] # install translations if qt_version_info < (5, 0): QtCore.QTextCodec.setCodecForTr( QtCore.QTextCodec.codecForName('utf-8')) # English translation as a fallback (to get correct plurals) lang_dir = pkg_resources.resource_filename('photini', 'data/lang') translator = QtCore.QTranslator(parent=app) if translator.load('photini.en', lang_dir): app.installTranslator(translator) translator = QtCore.QTranslator(parent=app) # localised translation, if it exists locale = QtCore.QLocale.system() if translator.load(locale, 'photini', '.', lang_dir): app.installTranslator(translator) translator = QtCore.QTranslator(parent=app) # Qt's own translation, e.g. for 'apply' or 'cancel' buttons if qt_version_info < (5, 0): if translator.load( locale, 'qt', '_', QtCore.QLibraryInfo.location( QtCore.QLibraryInfo.TranslationsPath)): app.installTranslator(translator) # parse remaining arguments version = 'Photini ' + __version__ + ', build ' + build version += '\n Python ' + sys.version version += '\n ' + gi_version version += '\n PyQt {}, Qt {}, using {}'.format( QtCore.PYQT_VERSION_STR, QtCore.QT_VERSION_STR, ('QtWebKit', 'QtWebEngine')[using_qtwebengine]) if spelling_version: version += '\n ' + spelling_version if FlickrUploader: from photini.flickr import flickr_version version += '\n ' + flickr_version version += '\n available styles: {}'.format(', '.join( QtWidgets.QStyleFactory.keys())) version += '\n using style: {}'.format( QtWidgets.QApplication.style().objectName()) parser = OptionParser(usage=six.text_type( QtCore.QCoreApplication.translate( 'main', 'Usage: %prog [options] [file_name, ...]')), version=version, description=six.text_type( QtCore.QCoreApplication.translate( 'main', 'Photini photo metadata editor'))) parser.add_option('-t', '--test', action='store_true', help=six.text_type( QtCore.QCoreApplication.translate( 'main', 'test new features or API versions'))) parser.add_option('-v', '--verbose', action='count', default=0, help=six.text_type( QtCore.QCoreApplication.translate( 'main', 'increase number of logging messages'))) options, args = parser.parse_args() # create GUI and run application event loop main = MainWindow(options, args) main.show() return app.exec_()
def __init__(self, image_list, parent=None): super(TabWidget, self).__init__(parent) self.app = QtWidgets.QApplication.instance() self.app.aboutToQuit.connect(self.stop_copy) if gp and self.app.options.test: self.gp_log = gp.check_result(gp.use_python_logging()) self.config_store = self.app.config_store self.image_list = image_list self.setLayout(QtWidgets.QGridLayout()) form = QtWidgets.QFormLayout() form.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow) self.nm = NameMangler() self.file_data = {} self.file_list = [] self.source = None self.file_copier = None self.updating = QtCore.QMutex() # source selector box = QtWidgets.QHBoxLayout() box.setContentsMargins(0, 0, 0, 0) self.source_selector = QtWidgets.QComboBox() self.source_selector.currentIndexChanged.connect(self.new_source) self.source_selector.setContextMenuPolicy(Qt.CustomContextMenu) self.source_selector.customContextMenuRequested.connect( self.remove_folder) box.addWidget(self.source_selector) refresh_button = QtWidgets.QPushButton( translate('ImporterTab', 'refresh')) refresh_button.clicked.connect(self.refresh) box.addWidget(refresh_button) box.setStretch(0, 1) form.addRow(translate('ImporterTab', 'Source'), box) # update config self.config_store.delete('importer', 'folders') for section in self.config_store.config.sections(): if not section.startswith('importer'): continue path_format = self.config_store.get(section, 'path_format') if not (path_format and '(' in path_format): continue path_format = path_format.replace('(', '{').replace(')', '}') self.config_store.set(section, 'path_format', path_format) # path format self.path_format = QtWidgets.QLineEdit() self.path_format.setValidator(PathFormatValidator()) self.path_format.textChanged.connect(self.nm.new_format) self.path_format.editingFinished.connect(self.path_format_finished) form.addRow(translate('ImporterTab', 'Target format'), self.path_format) # path example self.path_example = QtWidgets.QLabel() self.nm.new_example.connect(self.path_example.setText) form.addRow('=>', self.path_example) self.layout().addLayout(form, 0, 0) # file list self.file_list_widget = QtWidgets.QListWidget() self.file_list_widget.setSelectionMode( QtWidgets.QAbstractItemView.ExtendedSelection) self.file_list_widget.itemSelectionChanged.connect( self.selection_changed) self.layout().addWidget(self.file_list_widget, 1, 0) # selection buttons buttons = QtWidgets.QVBoxLayout() buttons.addStretch(1) self.selected_count = QtWidgets.QLabel() buttons.addWidget(self.selected_count) select_all = QtWidgets.QPushButton( translate('ImporterTab', 'Select\nall')) select_all.clicked.connect(self.select_all) buttons.addWidget(select_all) select_new = QtWidgets.QPushButton( translate('ImporterTab', 'Select\nnew')) select_new.clicked.connect(self.select_new) buttons.addWidget(select_new) # copy buttons self.move_button = StartStopButton( translate('ImporterTab', 'Move\nphotos'), translate('ImporterTab', 'Stop\nmove')) self.move_button.click_start.connect(self.move_selected) self.move_button.click_stop.connect(self.stop_copy) buttons.addWidget(self.move_button) self.copy_button = StartStopButton( translate('ImporterTab', 'Copy\nphotos'), translate('ImporterTab', 'Stop\ncopy')) self.copy_button.click_start.connect(self.copy_selected) self.copy_button.click_stop.connect(self.stop_copy) buttons.addWidget(self.copy_button) self.layout().addLayout(buttons, 0, 1, 2, 1) self.selection_changed() # final initialisation self.image_list.sort_order_changed.connect(self.sort_file_list) path = QtCore.QStandardPaths.writableLocation( QtCore.QStandardPaths.PicturesLocation) self.path_format.setText(os.path.join(path, '%Y', '%Y_%m_%d', '{name}'))
def __init__(self, path, image_list, thumb_size=80, *arg, **kw): super(Image, self).__init__(*arg, **kw) self.path = path self.image_list = image_list self.name, ext = os.path.splitext(os.path.basename(self.path)) self.selected = False self.thumb_size = thumb_size # read image with open(self.path, 'rb') as pf: image_data = pf.read() # read metadata self.metadata = Metadata( self.path, image_data, new_status=self.show_status) # set file type ext = ext.lower() self.file_type = imghdr.what(self.path) or 'raw' if self.file_type == 'tiff' and ext not in ('.tif', '.tiff'): self.file_type = 'raw' # make 'master' thumbnail self.pixmap = QtGui.QPixmap() self.pixmap.loadFromData(image_data) unrotate = self.file_type == 'raw' if self.pixmap.isNull(): # image read failed so attempt to use exif thumbnail thumb = self.metadata.get_exif_thumbnail() if thumb: self.pixmap.loadFromData(bytearray(thumb)) unrotate = False if not self.pixmap.isNull(): if max(self.pixmap.width(), self.pixmap.height()) > 450: # store a scaled down version of image to save memory self.pixmap = self.pixmap.scaled( 300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation) if unrotate: # loading preview which is already re-oriented orientation = self.metadata.orientation if orientation and orientation.value > 1: # need to unrotate and or unreflect image transform = QtGui.QTransform() if orientation.value in (3, 4): transform = transform.rotate(180.0) elif orientation.value in (5, 6): transform = transform.rotate(-90.0) elif orientation.value in (7, 8): transform = transform.rotate(90.0) if orientation.value in (2, 4, 5, 7): transform = transform.scale(-1.0, 1.0) self.pixmap = self.pixmap.transformed(transform) # sub widgets layout = QtWidgets.QGridLayout() layout.setSpacing(0) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) self.setToolTip(self.path) # label to display image self.image = QtWidgets.QLabel() self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) layout.addWidget(self.image, 0, 0, 1, 2) # label to display file name self.label = QtWidgets.QLabel() self.label.setAlignment(Qt.AlignRight) self.label.setStyleSheet("QLabel { font-size: 12px }") layout.addWidget(self.label, 1, 1) # label to display status self.status = QtWidgets.QLabel() self.status.setAlignment(Qt.AlignLeft) self.status.setStyleSheet("QLabel { font-size: 12px }") self.status.setFont(QtGui.QFont("Dejavu Sans")) if not self.status.fontInfo().exactMatch(): # probably on Windows, try a different font self.status.setFont(QtGui.QFont("Segoe UI Symbol")) layout.addWidget(self.status, 1, 0) self.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Plain) self.setObjectName("thumbnail") self.set_selected(False) self.show_status(False) self._set_thumb_size(self.thumb_size)
def __init__(self, image_list, parent=None): super(PhotiniMap, self).__init__(parent) self.logger = logging.getLogger(self.__class__.__name__) self.app = QtWidgets.QApplication.instance() self.config_store = self.app.config_store self.image_list = image_list self.script_dir = pkg_resources.resource_filename( 'photini', 'data/' + self.__class__.__name__.lower() + '/') self.drag_icon = QtGui.QPixmap( os.path.join(self.script_dir, 'grey_marker.png')) self.search_string = None self.map_loaded = False self.marker_images = {} self.map_status = {} self.setChildrenCollapsible(False) left_side = QtWidgets.QWidget() self.addWidget(left_side) self.grid = QtWidgets.QGridLayout() self.grid.setContentsMargins(0, 0, 0, 0) self.grid.setRowStretch(6, 1) self.grid.setColumnStretch(1, 1) left_side.setLayout(self.grid) # map self.map = WebView() self.map.setPage(WebPage(parent=self.map)) if QtWebEngineWidgets: self.web_channel = QtWebChannel.QWebChannel() self.map.page().setWebChannel(self.web_channel) self.web_channel.registerObject('python', self) else: self.map.page().setLinkDelegationPolicy( QtWebKitWidgets.QWebPage.DelegateAllLinks) self.map.page().linkClicked.connect(self.link_clicked) self.map.page().mainFrame().javaScriptWindowObjectCleared.connect( self.java_script_window_object_cleared) self.map.setAcceptDrops(False) self.map.drop_text.connect(self.drop_text) self.addWidget(self.map) # search self.grid.addWidget( QtWidgets.QLabel(translate('PhotiniMap', 'Search:')), 0, 0) self.edit_box = QtWidgets.QComboBox() self.edit_box.setMinimumWidth(200) self.edit_box.setEditable(True) self.edit_box.setInsertPolicy(QtWidgets.QComboBox.NoInsert) self.edit_box.lineEdit().setPlaceholderText( translate('PhotiniMap', '<new search>')) self.edit_box.lineEdit().returnPressed.connect(self.search) self.edit_box.activated.connect(self.goto_search_result) self.clear_search() self.edit_box.setEnabled(False) self.grid.addWidget(self.edit_box, 0, 1) # latitude & longitude self.grid.addWidget( QtWidgets.QLabel(translate('PhotiniMap', 'Lat, long:')), 1, 0) self.coords = SingleLineEdit() self.coords.editingFinished.connect(self.new_coords) self.coords.setEnabled(False) self.grid.addWidget(self.coords, 1, 1) # convert lat/lng to location info self.auto_location = QtWidgets.QPushButton( translate('PhotiniMap', 'Lat, long -> location')) self.auto_location.setEnabled(False) self.auto_location.clicked.connect(self.get_address) self.grid.addWidget(self.auto_location, 2, 1) # location info self.location_info = LocationInfo() self.location_info['taken'].new_value.connect(self.new_location_taken) self.location_info['shown'].new_value.connect(self.new_location_shown) self.location_info.swap.clicked.connect(self.swap_locations) self.location_info.setEnabled(False) self.grid.addWidget(self.location_info, 3, 0, 1, 2) # load map button self.load_map = QtWidgets.QPushButton( translate('PhotiniMap', '\nLoad map\n')) self.load_map.clicked.connect(self.initialise) self.grid.addWidget(self.load_map, 7, 0, 1, 2) # other init self.image_list.image_list_changed.connect(self.image_list_changed) self.splitterMoved.connect(self.new_split)
def __init__(self, options, initial_files): super(MainWindow, self).__init__() self.setWindowTitle(self.tr("Photini photo metadata editor")) pixmap = QtGui.QPixmap() pixmap.loadFromData( pkg_resources.resource_string('photini', 'data/icons/48/photini.png')) icon = QtGui.QIcon(pixmap) self.setWindowIcon(icon) self.selection = list() # logger window self.loggerwindow = LoggerWindow(options.verbose) self.loggerwindow.setWindowIcon(icon) self.logger = logging.getLogger(self.__class__.__name__) # set network proxy proxies = getproxies() if 'http' in proxies: parsed = urlparse(proxies['http']) QNetworkProxy.setApplicationProxy( QNetworkProxy(QNetworkProxy.HttpProxy, parsed.hostname, parsed.port)) # create shared global objects self.app = QtWidgets.QApplication.instance() self.app.config_store = ConfigStore('editor', parent=self) self.app.spell_check = SpellCheck(parent=self) self.app.test_mode = options.test # set debug mode if self.app.test_mode: debug_metadata() # restore size size = self.width(), self.height() self.resize( *eval(self.app.config_store.get('main_window', 'size', str(size)))) # image selector self.image_list = ImageList() self.image_list.selection_changed.connect(self.new_selection) self.image_list.new_metadata.connect(self.new_metadata) # prepare list of tabs and associated stuff self.tab_list = ( { 'name': self.tr('&Descriptive metadata'), 'key': 'descriptive_metadata', 'class': Descriptive }, { 'name': self.tr('&Technical metadata'), 'key': 'technical_metadata', 'class': Technical }, { 'name': self.tr('Map (&Google)'), 'key': 'map_google', 'class': GoogleMap }, { 'name': self.tr('Map (&Bing)'), 'key': 'map_bing', 'class': BingMap }, { 'name': self.tr('Map (&OSM)'), 'key': 'map_osm', 'class': OpenStreetMap }, { 'name': self.tr('&Flickr upload'), 'key': 'flickr_upload', 'class': FlickrUploader }, { 'name': self.tr('Google &Photos upload'), 'key': 'picasa_upload', 'class': PicasaUploader }, { 'name': self.tr('Faceboo&k upload'), 'key': 'facebook_upload', 'class': FacebookUploader }, { 'name': self.tr('&Import photos'), 'key': 'import_photos', 'class': Importer }, ) # file menu file_menu = self.menuBar().addMenu(self.tr('File')) open_action = QtWidgets.QAction(self.tr('Open images'), self) open_action.setShortcuts(QtGui.QKeySequence.Open) open_action.triggered.connect(self.image_list.open_files) file_menu.addAction(open_action) self.save_action = QtWidgets.QAction( self.tr('Save images with new data'), self) self.save_action.setShortcuts(QtGui.QKeySequence.Save) self.save_action.setEnabled(False) self.save_action.triggered.connect(self.image_list.save_files) file_menu.addAction(self.save_action) self.close_action = QtWidgets.QAction(self.tr('Close selected images'), self) self.close_action.setEnabled(False) self.close_action.triggered.connect(self.close_files) file_menu.addAction(self.close_action) close_all_action = QtWidgets.QAction(self.tr('Close all images'), self) close_all_action.triggered.connect(self.close_all_files) file_menu.addAction(close_all_action) file_menu.addSeparator() quit_action = QtWidgets.QAction(self.tr('Quit'), self) quit_action.setShortcuts( [QtGui.QKeySequence.Quit, QtGui.QKeySequence.Close]) quit_action.triggered.connect( QtWidgets.QApplication.instance().closeAllWindows) file_menu.addAction(quit_action) # options menu options_menu = self.menuBar().addMenu(self.tr('Options')) settings_action = QtWidgets.QAction(self.tr('Settings'), self) settings_action.triggered.connect(self.edit_settings) options_menu.addAction(settings_action) options_menu.addSeparator() for tab in self.tab_list: name = tab['name'].replace('&', '') tab['action'] = QtWidgets.QAction(name, self) tab['action'].setCheckable(True) if tab['class']: tab['action'].setChecked( eval(self.app.config_store.get('tabs', tab['key'], 'True'))) else: tab['action'].setEnabled(False) tab['action'].triggered.connect(self.add_tabs) options_menu.addAction(tab['action']) # spelling menu languages = self.app.spell_check.available_languages() spelling_menu = self.menuBar().addMenu(self.tr('Spelling')) enable_action = QtWidgets.QAction(self.tr('Enable spell check'), self) enable_action.setEnabled(bool(languages)) enable_action.setCheckable(True) enable_action.setChecked(self.app.spell_check.enabled) enable_action.toggled.connect(self.app.spell_check.enable) spelling_menu.addAction(enable_action) language_menu = QtWidgets.QMenu(self.tr('Choose language'), self) language_menu.setEnabled(bool(languages)) language_group = QtWidgets.QActionGroup(self) current_language = self.app.spell_check.current_language() for tag in languages: language_action = QtWidgets.QAction(tag, self) language_action.setCheckable(True) language_action.setChecked(tag == current_language) language_action.setActionGroup(language_group) language_menu.addAction(language_action) language_group.triggered.connect(self.app.spell_check.set_language) spelling_menu.addMenu(language_menu) # help menu help_menu = self.menuBar().addMenu(self.tr('Help')) about_action = QtWidgets.QAction(self.tr('About Photini'), self) about_action.triggered.connect(self.about) help_menu.addAction(about_action) help_menu.addSeparator() help_action = QtWidgets.QAction(self.tr('Photini documentation'), self) help_action.triggered.connect(self.open_docs) help_menu.addAction(help_action) # main application area self.central_widget = QtWidgets.QSplitter() self.central_widget.setOrientation(Qt.Vertical) self.central_widget.setChildrenCollapsible(False) self.tabs = QtWidgets.QTabWidget() self.tabs.setTabBar(QTabBar()) self.tabs.setElideMode(Qt.ElideRight) self.tabs.currentChanged.connect(self.new_tab) self.add_tabs() self.central_widget.addWidget(self.tabs) self.central_widget.addWidget(self.image_list) size = self.central_widget.sizes() self.central_widget.setSizes( eval(self.app.config_store.get('main_window', 'split', str(size)))) self.central_widget.splitterMoved.connect(self.new_split) self.setCentralWidget(self.central_widget) # open files given on command line, after GUI is displayed self.initial_files = initial_files if self.initial_files: QtCore.QTimer.singleShot(0, self.open_initial_files)
def __init__(self, options, initial_files): super(MainWindow, self).__init__() self.setWindowTitle(self.tr("Photini photo metadata editor")) pixmap = QtGui.QPixmap() pixmap.loadFromData(pkg_resources.resource_string( 'photini', 'data/icons/48/photini.png')) icon = QtGui.QIcon(pixmap) self.setWindowIcon(icon) self.selection = list() # logger window self.loggerwindow = LoggerWindow(options.verbose) self.loggerwindow.setWindowIcon(icon) # set network proxy proxies = getproxies() if 'http' in proxies: parsed = urlparse(proxies['http']) QNetworkProxy.setApplicationProxy(QNetworkProxy( QNetworkProxy.HttpProxy, parsed.hostname, parsed.port)) # create shared global objects self.app = QtWidgets.QApplication.instance() self.app.config_store = ConfigStore('editor', parent=self) self.app.spell_check = SpellCheck(parent=self) self.app.test_mode = options.test # restore size size = self.width(), self.height() self.resize(*eval( self.app.config_store.get('main_window', 'size', str(size)))) # image selector self.image_list = ImageList() self.image_list.selection_changed.connect(self.new_selection) self.image_list.new_metadata.connect(self.new_metadata) # update config file if self.app.config_store.config.has_section('tabs'): conv = { 'descriptive_metadata': 'photini.descriptive', 'technical_metadata' : 'photini.technical', 'map_google' : 'photini.googlemap', 'map_bing' : 'photini.bingmap', 'map_mapbox' : 'photini.mapboxmap', 'map_osm' : 'photini.openstreetmap', 'flickr_upload' : 'photini.flickr', 'import_photos' : 'photini.importer', } for key in self.app.config_store.config.options('tabs'): if key in conv: self.app.config_store.set( 'tabs', conv[key], self.app.config_store.get('tabs', key)) self.app.config_store.config.remove_option('tabs', key) # prepare list of tabs and associated stuff self.tab_list = [] modules = ('photini.descriptive', 'photini.technical', 'photini.googlemap', 'photini.bingmap', 'photini.mapboxmap', 'photini.openstreetmap', 'photini.flickr', 'photini.googlephotos', 'photini.importer') modules = eval(self.app.config_store.get( 'tabs', 'modules', pprint.pformat(modules))) for module in modules: tab = {'module': module} try: mod = importlib.import_module(tab['module']) tab['class'] = mod.TabWidget tab['name'] = tab['class'].tab_name() except ImportError as ex: print(str(ex)) tab['class'] = None self.tab_list.append(tab) # file menu file_menu = self.menuBar().addMenu(self.tr('File')) open_action = QtWidgets.QAction(self.tr('Open images'), self) open_action.setShortcuts(QtGui.QKeySequence.Open) open_action.triggered.connect(self.image_list.open_files) file_menu.addAction(open_action) self.save_action = QtWidgets.QAction( self.tr('Save images with new data'), self) self.save_action.setShortcuts(QtGui.QKeySequence.Save) self.save_action.setEnabled(False) self.save_action.triggered.connect(self.image_list.save_files) file_menu.addAction(self.save_action) self.close_action = QtWidgets.QAction( self.tr('Close selected images'), self) self.close_action.setEnabled(False) self.close_action.triggered.connect(self.close_files) file_menu.addAction(self.close_action) close_all_action = QtWidgets.QAction(self.tr('Close all images'), self) close_all_action.triggered.connect(self.close_all_files) file_menu.addAction(close_all_action) if GpxImporter: file_menu.addSeparator() self.import_gpx_action = QtWidgets.QAction( self.tr('Import GPX file'), self) self.import_gpx_action.triggered.connect(self.import_pgx_file) file_menu.addAction(self.import_gpx_action) else: self.import_gpx_action = None file_menu.addSeparator() quit_action = QtWidgets.QAction(self.tr('Quit'), self) quit_action.setShortcuts( [QtGui.QKeySequence.Quit, QtGui.QKeySequence.Close]) quit_action.triggered.connect( QtWidgets.QApplication.instance().closeAllWindows) file_menu.addAction(quit_action) # options menu options_menu = self.menuBar().addMenu(self.tr('Options')) settings_action = QtWidgets.QAction(self.tr('Settings'), self) settings_action.triggered.connect(self.edit_settings) options_menu.addAction(settings_action) options_menu.addSeparator() for tab in self.tab_list: if tab['class']: name = tab['name'].replace('&', '') else: name = tab['module'] tab['action'] = QtWidgets.QAction(name, self) tab['action'].setCheckable(True) if tab['class']: tab['action'].setChecked(eval( self.app.config_store.get('tabs', tab['module'], 'True'))) else: tab['action'].setEnabled(False) tab['action'].triggered.connect(self.add_tabs) options_menu.addAction(tab['action']) # spelling menu languages = self.app.spell_check.available_languages() spelling_menu = self.menuBar().addMenu(self.tr('Spelling')) enable_action = QtWidgets.QAction(self.tr('Enable spell check'), self) enable_action.setEnabled(languages is not None) enable_action.setCheckable(True) enable_action.setChecked(self.app.spell_check.enabled) enable_action.toggled.connect(self.app.spell_check.enable) spelling_menu.addAction(enable_action) language_menu = QtWidgets.QMenu(self.tr('Choose language'), self) language_menu.setEnabled(languages is not None) current_language = self.app.spell_check.current_language() if languages: language_group = QtWidgets.QActionGroup(self) for name, code in languages: if name != code: name = code + ': ' + name language_action = QtWidgets.QAction(name, self) language_action.setCheckable(True) language_action.setChecked(code == current_language) language_action.setData(code) language_action.setActionGroup(language_group) language_menu.addAction(language_action) language_group.triggered.connect(self.set_language) else: language_action = QtWidgets.QAction( self.tr('No dictionary installed'), self) language_action.setEnabled(False) language_menu.addAction(language_action) spelling_menu.addMenu(language_menu) # help menu help_menu = self.menuBar().addMenu(self.tr('Help')) about_action = QtWidgets.QAction(self.tr('About Photini'), self) about_action.triggered.connect(self.about) help_menu.addAction(about_action) help_menu.addSeparator() help_action = QtWidgets.QAction(self.tr('Photini documentation'), self) help_action.triggered.connect(self.open_docs) help_menu.addAction(help_action) # main application area self.central_widget = QtWidgets.QSplitter() self.central_widget.setOrientation(Qt.Vertical) self.central_widget.setChildrenCollapsible(False) self.tabs = QtWidgets.QTabWidget() self.tabs.setTabBar(QTabBar()) self.tabs.setElideMode(Qt.ElideRight) self.tabs.currentChanged.connect(self.new_tab) self.add_tabs(False) self.central_widget.addWidget(self.tabs) self.central_widget.addWidget(self.image_list) size = self.central_widget.sizes() self.central_widget.setSizes(eval( self.app.config_store.get('main_window', 'split', str(size)))) self.central_widget.splitterMoved.connect(self.new_split) self.setCentralWidget(self.central_widget) # open files given on command line, after GUI is displayed self.initial_files = initial_files if self.initial_files: QtCore.QTimer.singleShot(0, self.open_initial_files)