def new_set(self): dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(translate('FlickrTab', 'Create new Flickr album')) dialog.setLayout(QtWidgets.QFormLayout()) title = SingleLineEdit(spell_check=True) dialog.layout().addRow(translate('FlickrTab', 'Title'), title) description = MultiLineEdit(spell_check=True) dialog.layout().addRow(translate('FlickrTab', 'Description'), description) dialog.layout().addRow( QtWidgets.QLabel( translate('FlickrTab', 'Album will be created when photos are uploaded'))) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addRow(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return title = title.toPlainText() if not title: return description = description.toPlainText() widget = self.upload_config.add_set(title, description, None, index=0) widget.setChecked(True)
def new_set(self): dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(self.tr('Create new Flickr album')) dialog.setLayout(QtWidgets.QFormLayout()) title = SingleLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Title'), title) description = MultiLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Description'), description) dialog.layout().addRow(QtWidgets.QLabel( self.tr('Album will be created when photos are uploaded'))) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addRow(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return title = title.toPlainText() if not title: return description = description.toPlainText() widget = self.upload_config.add_set(title, description, index=0) widget.setChecked(True) self.photosets.insert(0, { 'id' : None, 'title' : title, 'description' : description, 'widget' : widget, })
def __init__(self, *arg, **kw): super(KeywordsEditor, self).__init__(*arg, **kw) self.config_store = QtWidgets.QApplication.instance().config_store self.league_table = defaultdict(int) for keyword, score in eval( self.config_store.get('descriptive', 'keywords', '{}')).items(): self.league_table[keyword] = score layout = QtWidgets.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) # line edit box self.edit = SingleLineEdit(spell_check=True) layout.addWidget(self.edit) # favourites drop down self.favourites = ComboBox() self.update_favourites() self.favourites.setFixedWidth(self.favourites.title_width()) self.favourites.currentIndexChanged.connect(self.add_favourite) layout.addWidget(self.favourites) # adopt child widget methods and signals self.get_value = self.edit.get_value self.set_value = self.edit.set_value self.set_multiple = self.edit.set_multiple self.is_multiple = self.edit.is_multiple self.editingFinished = self.edit.editingFinished
def __init__(self, **kw): super(KeywordsEditor, self).__init__() self.config_store = QtWidgets.QApplication.instance().config_store self.league_table = {} for keyword, score in self.config_store.get('descriptive', 'keywords', {}).items(): if isinstance(score, int): # old style keyword list self.league_table[keyword] = date.min.isoformat(), score // 50 else: # new style keyword list self.league_table[keyword] = score layout = QtWidgets.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) # line edit box self.edit = SingleLineEdit(**kw) layout.addWidget(self.edit) # favourites drop down self.favourites = ComboBox() self.favourites.addItem(translate('DescriptiveTab', '<favourites>')) self.favourites.setFixedWidth( self.favourites.minimumSizeHint().width()) self.update_favourites() self.favourites.currentIndexChanged.connect(self.add_favourite) layout.addWidget(self.favourites) # adopt child widget methods and signals self.get_value = self.edit.get_value self.set_value = self.edit.set_value self.set_multiple = self.edit.set_multiple self.is_multiple = self.edit.is_multiple self.editingFinished = self.edit.editingFinished
def __init__(self, *args, **kw): super(LocationInfo, self).__init__(*args, **kw) layout = QtWidgets.QGridLayout() self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) self.members = {} for key in ('sublocation', 'city', 'province_state', 'country_name', 'country_code', 'world_region'): self.members[key] = SingleLineEdit() self.members[key].editingFinished.connect(self.editing_finished) self.members['country_code'].setMaximumWidth(40) for j, text in enumerate(( translate('AddressTab', 'Street'), translate('AddressTab', 'City'), translate('AddressTab', 'Province'), translate('AddressTab', 'Country'), translate('AddressTab', 'Region'), )): label = QtWidgets.QLabel(text) label.setAlignment(Qt.AlignRight) layout.addWidget(label, j, 0) layout.addWidget(self.members['sublocation'], 0, 1, 1, 2) layout.addWidget(self.members['city'], 1, 1, 1, 2) layout.addWidget(self.members['province_state'], 2, 1, 1, 2) layout.addWidget(self.members['country_name'], 3, 1) layout.addWidget(self.members['country_code'], 3, 2) layout.addWidget(self.members['world_region'], 4, 1, 1, 2) layout.setRowStretch(5, 1)
def __init__(self, *args, **kw): super(LocationInfo, self).__init__(*args, **kw) layout = QtWidgets.QGridLayout() self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) self.members = {} for key in ('SubLocation', 'City', 'ProvinceState', 'CountryName', 'CountryCode', 'WorldRegion'): self.members[key] = SingleLineEdit( length_check=ImageMetadata.max_bytes(key)) self.members[key].editingFinished.connect(self.editing_finished) self.members['CountryCode'].setMaximumWidth( width_for_text(self.members['CountryCode'], 'W' * 4)) self.members['SubLocation'].setToolTip( translate('AddressTab', 'Enter the name of the sublocation.')) self.members['City'].setToolTip( translate('AddressTab', 'Enter the name of the city.')) self.members['ProvinceState'].setToolTip( translate('AddressTab', 'Enter the name of the province or state.')) self.members['CountryName'].setToolTip( translate('AddressTab', 'Enter the name of the country.')) self.members['CountryCode'].setToolTip( translate( 'AddressTab', 'Enter the 2 or 3 letter ISO 3166 country code of the country.' )) self.members['WorldRegion'].setToolTip( translate('AddressTab', 'Enter the name of the world region.')) for j, text in enumerate(( translate('AddressTab', 'Street'), translate('AddressTab', 'City'), translate('AddressTab', 'Province'), translate('AddressTab', 'Country'), translate('AddressTab', 'Region'), )): label = QtWidgets.QLabel(text) label.setAlignment(Qt.AlignRight) layout.addWidget(label, j, 0) layout.addWidget(self.members['SubLocation'], 0, 1, 1, 2) layout.addWidget(self.members['City'], 1, 1, 1, 2) layout.addWidget(self.members['ProvinceState'], 2, 1, 1, 2) layout.addWidget(self.members['CountryName'], 3, 1) layout.addWidget(self.members['CountryCode'], 3, 2) layout.addWidget(self.members['WorldRegion'], 4, 1, 1, 2) layout.setRowStretch(5, 1)
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.form = QtWidgets.QFormLayout() self.setLayout(self.form) # construct widgets self.widgets = {} # title self.widgets['title'] = SingleLineEdit(spell_check=True) self.widgets['title'].editingFinished.connect(self.new_title) self.form.addRow(translate('DescriptiveTab', '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(translate('DescriptiveTab', 'Description / Caption'), self.widgets['description']) # keywords self.widgets['keywords'] = KeywordsEditor() self.widgets['keywords'].editingFinished.connect(self.new_keywords) self.form.addRow(translate('DescriptiveTab', 'Keywords'), self.widgets['keywords']) self.image_list.image_list_changed.connect(self.image_list_changed) # rating self.widgets['rating'] = RatingWidget() self.widgets['rating'].editing_finished.connect(self.new_rating) self.form.addRow(translate('DescriptiveTab', 'Rating'), self.widgets['rating']) # copyright self.widgets['copyright'] = LineEditWithAuto() self.widgets['copyright'].editingFinished.connect(self.new_copyright) self.widgets['copyright'].autoComplete.connect(self.auto_copyright) self.form.addRow(translate('DescriptiveTab', '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(translate('DescriptiveTab', 'Creator / Artist'), self.widgets['creator']) # disable until an image is selected self.setEnabled(False)
def __init__(self, **kw): super(LineEditWithAuto, self).__init__() self._is_multiple = False layout = QtWidgets.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) # line edit box self.edit = SingleLineEdit(**kw) layout.addWidget(self.edit) # auto complete button self.auto = QtWidgets.QPushButton(translate('DescriptiveTab', 'Auto')) layout.addWidget(self.auto) # adopt child widget methods and signals self.set_value = self.edit.set_value self.get_value = self.edit.get_value self.set_multiple = self.edit.set_multiple self.is_multiple = self.edit.is_multiple self.editingFinished = self.edit.editingFinished self.autoComplete = self.auto.clicked
def new_album(self): dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle(self.tr('Create new Facebook album')) dialog.setLayout(QtWidgets.QFormLayout()) name = SingleLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Title'), name) message = MultiLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Description'), message) location = SingleLineEdit(spell_check=True) dialog.layout().addRow(self.tr('Location'), location) privacy = QtWidgets.QComboBox() for display_name, value in ( (self.tr('Only me'), '{value: "SELF"}'), (self.tr('All friends'), '{value: "ALL_FRIENDS"}'), (self.tr('Friends of friends'), '{value: "FRIENDS_OF_FRIENDS"}'), (self.tr('Friends + networks'), '{value: "NETWORKS_FRIENDS"}'), (self.tr('Everyone'), '{value: "EVERYONE"}'), ): privacy.addItem(display_name, value) dialog.layout().addRow(self.tr('Privacy'), privacy) button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) dialog.layout().addRow(button_box) if dialog.exec_() != QtWidgets.QDialog.Accepted: return if not self.authorise('write'): self.refresh(force=True) return name = name.toPlainText().strip() if not name: return data = {'name': name} message = message.toPlainText().strip() if message: data['message'] = message location = location.toPlainText().strip() if location: data['location'] = location data['privacy'] = privacy.itemData(privacy.currentIndex()) try: album = self.session.post('https://graph.facebook.com/me/albums', data=data) except Exception as ex: self.logger.error(str(ex)) self.refresh(force=True) return self.load_user_data(album_id=album['id'])
def __init__(self, *args, **kw): super(LocationWidgets, self).__init__(*args, **kw) self.members = { 'sublocation': SingleLineEdit(), 'city': SingleLineEdit(), 'province_state': SingleLineEdit(), 'country_name': SingleLineEdit(), 'country_code': SingleLineEdit(), 'world_region': SingleLineEdit(), } self.members['sublocation'].editingFinished.connect( self.new_sublocation) self.members['city'].editingFinished.connect(self.new_city) self.members['province_state'].editingFinished.connect( self.new_province_state) self.members['country_name'].editingFinished.connect( self.new_country_name) self.members['country_code'].editingFinished.connect( self.new_country_code) self.members['world_region'].editingFinished.connect( self.new_world_region) self.members['country_code'].setMaximumWidth(40)
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)
class PhotiniMap(QtWidgets.QSplitter): 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) @QtCore.pyqtSlot(int, six.text_type) def log(self, level, message): self.logger.log(level, message) @QtCore.pyqtSlot(int, int) def new_split(self, pos, index): self.app.config_store.set('map', 'split', str(self.sizes())) @QtCore.pyqtSlot() def java_script_window_object_cleared(self): self.map.page().mainFrame().addToJavaScriptWindowObject("python", self) @QtCore.pyqtSlot(QtCore.QUrl) def link_clicked(self, url): if url.isLocalFile(): url.setScheme('http') webbrowser.open_new(url.toString()) @QtCore.pyqtSlot() def image_list_changed(self): self.redraw_markers() self.display_coords() self.display_location() self.see_selection() @QtCore.pyqtSlot() def initialise(self): page = ''' <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <style type="text/css"> html, body {{ height: 100%; margin: 0; padding: 0 }} #mapDiv {{ position: relative; width: 100%; height: 100% }} </style> {initialize} {head} <script type="text/javascript" src="script.js"></script> </head> <body ondragstart="return false"> <div id="mapDiv"></div> <script type="text/javascript"> var initData = {data}; </script> {body} </body> </html> ''' lat, lng = eval(self.config_store.get('map', 'centre', '(51.0, 0.0)')) zoom = eval(self.config_store.get('map', 'zoom', '11')) data = {'lat': lat, 'lng': lng, 'zoom': zoom} if QtWebEngineWidgets: initialize = ''' <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"> </script> <script type="text/javascript"> var python; function initialize() { new QWebChannel(qt.webChannelTransport, function (channel) { python = channel.objects.python; loadMap(); }); } </script> ''' else: initialize = ''' <script type="text/javascript"> function initialize() { loadMap(); } </script> ''' page = page.format(data=str(data), initialize=initialize, **self.get_page_elements()) QtWidgets.QApplication.setOverrideCursor(Qt.WaitCursor) self.map.setHtml(page, QtCore.QUrl.fromLocalFile(self.script_dir)) @QtCore.pyqtSlot() def initialize_finished(self): QtWidgets.QApplication.restoreOverrideCursor() self.map_loaded = True self.grid.removeWidget(self.load_map) self.load_map.setParent(None) show_terms = self.show_terms() self.grid.addLayout(show_terms, 7, 0, 1, 2) self.edit_box.setEnabled(True) self.map.setAcceptDrops(True) self.image_list.set_drag_to_map(self.drag_icon) self.redraw_markers() self.display_coords() def refresh(self): self.setSizes( eval(self.app.config_store.get('map', 'split', str(self.sizes())))) if not self.map_loaded: return lat, lng = eval(self.config_store.get('map', 'centre')) zoom = eval(self.config_store.get('map', 'zoom')) self.JavaScript('setView({0}, {1}, {2:d})'.format( repr(lat), repr(lng), zoom)) self.redraw_markers() self.image_list.set_drag_to_map(self.drag_icon) def do_not_close(self): return False @QtCore.pyqtSlot(QtCore.QVariant) def new_status(self, status): self.map_status = status self.config_store.set('map', 'centre', str(self.map_status['centre'])) self.config_store.set('map', 'zoom', str(int(self.map_status['zoom']))) @QtCore.pyqtSlot(int, int, six.text_type) def drop_text(self, x, y, text): self.JavaScript('markerDrop({:d},{:d},{:s})'.format(x, y, text)) @QtCore.pyqtSlot(float, float, QtCore.QVariant) def marker_drop(self, lat, lng, path_list): for path in path_list: image = self.image_list.get_image(path) self._remove_image(image) self._set_metadata(image, lat, lng) self._add_image(image) self.display_coords() self.see_selection() @QtCore.pyqtSlot() def new_coords(self): text = self.coords.get_value().strip() if not text: for image in self.image_list.get_selected_images(): self._remove_image(image) image.metadata.latlong = None return try: lat, lng = list(map(float, text.split(','))) except Exception: self.display_coords() return for image in self.image_list.get_selected_images(): self._remove_image(image) self._set_metadata(image, lat, lng) self._add_image(image) self.display_coords() self.see_selection() def see_selection(self): locations = [] for image in self.image_list.get_selected_images(): latlong = image.metadata.latlong if not latlong: continue location = [latlong.lat, latlong.lon] if location not in locations: locations.append(location) if not locations: return self.JavaScript('fitPoints({})'.format(repr(locations))) @QtCore.pyqtSlot(six.text_type, six.text_type, six.text_type, six.text_type, six.text_type, six.text_type) def set_location_taken(self, world_region, country_code, country_name, province_state, city, sublocation): for image in self.image_list.get_selected_images(): image.metadata.location_taken = (sublocation, city, province_state, country_name, country_code, world_region) self.display_location() @QtCore.pyqtSlot() def swap_locations(self): for image in self.image_list.get_selected_images(): taken = image.metadata.location_taken if taken: taken = taken.value shown = image.metadata.location_shown if shown: shown = shown.value image.metadata.location_taken = shown image.metadata.location_shown = taken self.display_location() @QtCore.pyqtSlot(six.text_type, six.text_type) def new_location_taken(self, key, value): self._new_location('location_taken', key, value) @QtCore.pyqtSlot(six.text_type, six.text_type) def new_location_shown(self, key, value): self._new_location('location_shown', key, value) def _new_location(self, taken_shown, key, value): for image in self.image_list.get_selected_images(): location = getattr(image.metadata, taken_shown) if location: new_value = dict(location.value) else: new_value = dict.fromkeys( ('sublocation', 'city', 'province_state', 'country_name', 'country_code', 'world_region')) new_value[key] = value if not any(new_value.values()): new_value = None setattr(image.metadata, taken_shown, new_value) self.display_location() def display_coords(self): images = self.image_list.get_selected_images() if not images: self.coords.set_value(None) self.auto_location.setEnabled(False) return latlong = images[0].metadata.latlong for image in images[1:]: if image.metadata.latlong != latlong: self.coords.set_multiple() self.auto_location.setEnabled(False) return self.coords.set_value(latlong) self.auto_location.setEnabled(self.map_loaded and bool(latlong)) def display_location(self): images = self.image_list.get_selected_images() if not images: for widget_group in (self.location_info['taken'], self.location_info['shown']): for attr in widget_group.members: widget_group[attr].set_value(None) return for taken_shown in 'taken', 'shown': widget_group = self.location_info[taken_shown] for attr in widget_group.members: value = getattr(images[0].metadata, 'location_' + taken_shown) if value: value = value.value[attr] for image in images[1:]: other = getattr(image.metadata, 'location_' + taken_shown) if other: other = other.value[attr] if other != value: widget_group[attr].set_multiple() break else: widget_group[attr].set_value(value) @QtCore.pyqtSlot(list) def new_selection(self, selection): self.coords.setEnabled(bool(selection)) self.location_info.setEnabled(bool(selection)) for marker_id, images in list(self.marker_images.items()): self.JavaScript('enableMarker("{}", {:d})'.format( marker_id, any([image.selected for image in images]))) self.display_coords() self.display_location() self.see_selection() def redraw_markers(self): self.JavaScript('removeMarkers()') self.marker_images = {} for image in self.image_list.get_images(): self._add_image(image) def _add_image(self, image): if not self.map_loaded: return latlong = image.metadata.latlong if not latlong: return for marker_id in self.marker_images: if self.marker_images[marker_id][0].metadata.latlong == latlong: self.marker_images[marker_id].append(image) if image.selected: self.JavaScript('enableMarker("{}", {:d})'.format( marker_id, True)) break else: for i in range(len(self.marker_images) + 2): marker_id = str(i) if marker_id not in self.marker_images: break self.marker_images[marker_id] = [image] self.JavaScript('addMarker("{}", {!r}, {!r}, {:d})'.format( marker_id, latlong.lat, latlong.lon, image.selected)) def _remove_image(self, image): for marker_id in self.marker_images: if image in self.marker_images[marker_id]: break else: return self.marker_images[marker_id].remove(image) if self.marker_images[marker_id]: self.JavaScript('enableMarker("{}", {:d})'.format( marker_id, any([ image.selected for image in self.marker_images[marker_id] ]))) else: self.JavaScript('delMarker("{}")'.format(marker_id)) del self.marker_images[marker_id] @QtCore.pyqtSlot() def get_address(self): latlng = self.coords.get_value() self.JavaScript('reverseGeocode({})'.format(latlng)) @QtCore.pyqtSlot() def search(self, search_string=None): if not search_string: search_string = self.edit_box.lineEdit().text() self.edit_box.clearEditText() if not search_string: return self.search_string = search_string self.clear_search() self.JavaScript('search("{0}")'.format(search_string)) def clear_search(self): self.edit_box.clear() self.edit_box.addItem('') if self.search_string: self.edit_box.addItem(translate('PhotiniMap', '<repeat search>')) @QtCore.pyqtSlot(float, float, float, float, six.text_type) def search_result(self, lat0, lng0, lat1, lng1, name): self.edit_box.addItem(name, (lat0, lng0, lat1, lng1)) self.edit_box.showPopup() @QtCore.pyqtSlot(int) def goto_search_result(self, idx): self.edit_box.setCurrentIndex(0) self.edit_box.clearFocus() if idx == 0: return if self.search_string and idx == 1: # repeat search self.search(self.search_string) return view = self.edit_box.itemData(idx) self.JavaScript('adjustBounds({},{},{},{})'.format(*view)) @QtCore.pyqtSlot(six.text_type) def marker_click(self, marker_id): self.image_list.select_images(self.marker_images[marker_id]) @QtCore.pyqtSlot(float, float, six.text_type) def marker_drag(self, lat, lng, marker_id): for image in self.marker_images[marker_id]: self._set_metadata(image, lat, lng) self.display_coords() def _set_metadata(self, image, lat, lng): image.metadata.latlong = lat, lng def JavaScript(self, command): if self.map_loaded: command = command.replace('\\', '\\\\') if QtWebEngineWidgets: self.map.page().runJavaScript(command) else: self.map.page().mainFrame().evaluateJavaScript(command)
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 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, image_list, *arg, **kw): super(TabWidget, self).__init__(*arg, **kw) self.config_store = QtWidgets.QApplication.instance().config_store self.image_list = image_list self.form = QtWidgets.QFormLayout() self.setLayout(QtWidgets.QVBoxLayout()) self.layout().addLayout(self.form) self.layout().addStretch(1) # construct widgets self.widgets = {} # title self.widgets['title'] = SingleLineEdit( spell_check=True, length_check=ImageMetadata.max_bytes('title')) self.widgets['title'].setToolTip( translate( 'DescriptiveTab', 'Enter a short verbal and human readable name' ' for the image, this may be the file name.')) self.widgets['title'].editingFinished.connect(self.new_title) self.form.addRow(translate('DescriptiveTab', 'Title / Object Name'), self.widgets['title']) # description self.widgets['description'] = MultiLineEdit( spell_check=True, length_check=ImageMetadata.max_bytes('description')) self.widgets['description'].setToolTip( translate( 'DescriptiveTab', 'Enter a "caption" describing the who, what,' ' and why of what is happening in this image,\nthis might include' ' names of people, and/or their role in the action that is taking' ' place within the image.')) self.widgets['description'].editingFinished.connect( self.new_description) self.form.addRow(translate('DescriptiveTab', 'Description / Caption'), self.widgets['description']) # keywords self.widgets['keywords'] = KeywordsEditor( spell_check=True, length_check=ImageMetadata.max_bytes('keywords'), multi_string=True) self.widgets['keywords'].setToolTip( translate( 'DescriptiveTab', 'Enter any number of keywords, terms or phrases' ' used to express the subject matter in the image.' '\nSeparate them with ";" characters.')) self.widgets['keywords'].editingFinished.connect(self.new_keywords) self.form.addRow(translate('DescriptiveTab', 'Keywords'), self.widgets['keywords']) self.image_list.image_list_changed.connect(self.image_list_changed) # rating self.widgets['rating'] = RatingWidget() self.widgets['rating'].editing_finished.connect(self.new_rating) self.form.addRow(translate('DescriptiveTab', 'Rating'), self.widgets['rating']) # copyright self.widgets['copyright'] = LineEditWithAuto( length_check=ImageMetadata.max_bytes('copyright')) self.widgets['copyright'].setToolTip( translate( 'OwnerTab', 'Enter a notice on the current owner of the' ' copyright for this image, such as "©2008 Jane Doe".')) self.widgets['copyright'].editingFinished.connect(self.new_copyright) self.widgets['copyright'].autoComplete.connect(self.auto_copyright) self.form.addRow(translate('DescriptiveTab', 'Copyright'), self.widgets['copyright']) # creator self.widgets['creator'] = LineEditWithAuto( length_check=ImageMetadata.max_bytes('creator'), multi_string=True) self.widgets['creator'].setToolTip( translate('OwnerTab', 'Enter the name of the person that created this image.')) self.widgets['creator'].editingFinished.connect(self.new_creator) self.widgets['creator'].autoComplete.connect(self.auto_creator) self.form.addRow(translate('DescriptiveTab', 'Creator / Artist'), self.widgets['creator']) # disable until an image is selected self.setEnabled(False)
class PhotiniMap(QtWidgets.QSplitter): 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 search_terms(self): return None @QtCore.pyqtSlot() @catch_all def load_tou_opencage(self): QtGui.QDesktopServices.openUrl( QtCore.QUrl('https://geocoder.opencagedata.com/')) @QtCore.pyqtSlot() @catch_all def load_tou_osm(self): QtGui.QDesktopServices.openUrl( QtCore.QUrl('http://www.openstreetmap.org/copyright')) @QtCore.pyqtSlot(int, int) @catch_all def new_split(self, pos, index): self.app.config_store.set('map', 'split', str(self.sizes())) @QtCore.pyqtSlot() @catch_all def image_list_changed(self): self.redraw_markers() self.display_coords() self.display_location() self.see_selection() @QtCore.pyqtSlot() @catch_all def initialise(self): page = ''' <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <style type="text/css"> html, body {{ height: 100%; margin: 0; padding: 0 }} #mapDiv {{ position: relative; width: 100%; height: 100% }} </style> <script type="text/javascript"> var initData = {{key: "{key}", lat: {lat}, lng: {lng}, zoom: {zoom}}}; </script> {initialize} {head} <script type="text/javascript" src="script.js" async></script> </head> <body ondragstart="return false"> <div id="mapDiv"></div> </body> </html> ''' lat, lng = eval( self.app.config_store.get('map', 'centre', '(51.0, 0.0)')) zoom = int(eval(self.app.config_store.get('map', 'zoom', '11'))) if using_qtwebengine: initialize = ''' <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"> </script> <script type="text/javascript"> var python; function initialize() { new QWebChannel(qt.webChannelTransport, function (channel) { python = channel.objects.python; loadMap(); }); } </script> ''' else: initialize = ''' <script type="text/javascript"> function initialize() { loadMap(); } </script> ''' page = page.format(lat=lat, lng=lng, zoom=zoom, initialize=initialize, key=self.api_key, head=self.get_head()) QtWidgets.QApplication.setOverrideCursor(Qt.WaitCursor) self.map.setHtml(page, QtCore.QUrl.fromLocalFile(self.script_dir)) @catch_all def initialize_finished(self): QtWidgets.QApplication.restoreOverrideCursor() self.map_loaded = True self.edit_box.setEnabled(True) self.map.setAcceptDrops(True) self.redraw_markers() self.display_coords() def refresh(self): self.setSizes( eval(self.app.config_store.get('map', 'split', str(self.sizes())))) self.image_list.set_drag_to_map(self.drag_icon, self.drag_hotspot) if not self.map_loaded: self.initialise() return lat, lng = eval(self.app.config_store.get('map', 'centre')) zoom = int(eval(self.app.config_store.get('map', 'zoom'))) self.JavaScript('setView({!r},{!r},{:d})'.format(lat, lng, zoom)) def do_not_close(self): return False @catch_all def new_status(self, status): self.map_status.update(status) for key in ('centre', 'zoom'): if key in status: self.app.config_store.set('map', key, repr(self.map_status[key])) @QtCore.pyqtSlot(int, int, six.text_type) @catch_all def drop_text(self, x, y, text): self.dropped_images = eval(text) self.JavaScript('markerDrop({:d},{:d})'.format(x, y)) @catch_all def marker_drop(self, lat, lng): for path in self.dropped_images: image = self.image_list.get_image(path) image.metadata.latlong = lat, lng self.dropped_images = [] self.redraw_markers() self.display_coords() self.see_selection() @QtCore.pyqtSlot() @catch_all def new_coords(self): text = self.coords.get_value().strip() if not text: for image in self.image_list.get_selected_images(): image.metadata.latlong = None self.redraw_markers() return try: lat, lng = map(float, text.split(',')) except Exception: self.display_coords() return for image in self.image_list.get_selected_images(): image.metadata.latlong = lat, lng self.redraw_markers() self.display_coords() self.see_selection() def see_selection(self): locations = [] for image in self.image_list.get_selected_images(): latlong = image.metadata.latlong if not latlong: continue location = [latlong.lat, latlong.lon] if location not in locations: locations.append(location) if not locations: return self.JavaScript('fitPoints({})'.format(repr(locations))) @QtCore.pyqtSlot(QtGui.QContextMenuEvent) @catch_all def location_tab_context_menu(self, event): idx = self.location_info.tabBar().tabAt(event.pos()) self.location_info.setCurrentIndex(idx) menu = QtWidgets.QMenu(self) menu.addAction(self.tr('Duplicate location'), self.duplicate_location) menu.addAction(self.tr('Delete location'), self.delete_location) action = menu.exec_(event.globalPos()) @QtCore.pyqtSlot() @catch_all def duplicate_location(self): idx = self.location_info.currentIndex() for image in self.image_list.get_selected_images(): # duplicate data location = Location(self._get_location(image, idx) or {}) # shuffle data up location_list = list(image.metadata.location_shown or []) location_list.insert(idx, location) image.metadata.location_shown = location_list # display data self.display_location() @QtCore.pyqtSlot() @catch_all def delete_location(self): idx = self.location_info.currentIndex() for image in self.image_list.get_selected_images(): # shuffle data down location_list = list(image.metadata.location_shown or []) if idx == 0: if location_list: location = location_list[0] else: location = None image.metadata.location_taken = location if idx <= len(location_list): del location_list[max(idx - 1, 0)] image.metadata.location_shown = location_list # display data self.display_location() @QtCore.pyqtSlot(int, int) @catch_all def location_tab_moved(self, idx_a, idx_b): self.pending_move = idx_a, idx_b # do actual swap when idle to avoid seg fault QtCore.QTimer.singleShot(0, self._location_tab_moved) @QtCore.pyqtSlot() @catch_all def _location_tab_moved(self): idx_a, idx_b = self.pending_move # swap data for image in self.image_list.get_selected_images(): temp_a = self._get_location(image, idx_a) temp_b = self._get_location(image, idx_b) self._set_location(image, idx_a, temp_b) self._set_location(image, idx_b, temp_a) # adjust tab names for idx in range(min(idx_a, idx_b), max(idx_a, idx_b) + 1): self.set_tab_text(idx) # display data self.display_location() def _get_location(self, image, idx): if idx == 0: return image.metadata.location_taken elif not image.metadata.location_shown: return None elif idx <= len(image.metadata.location_shown): return image.metadata.location_shown[idx - 1] return None def _set_location(self, image, idx, location): if idx == 0: image.metadata.location_taken = location else: location_list = list(image.metadata.location_shown or []) while len(location_list) < idx: location_list.append(None) location_list[idx - 1] = location image.metadata.location_shown = location_list @QtCore.pyqtSlot(object, dict) @catch_all def new_location(self, widget, new_value): idx = self.location_info.indexOf(widget) for image in self.image_list.get_selected_images(): temp = dict(self._get_location(image, idx) or {}) temp.update(new_value) self._set_location(image, idx, temp) # new_location can be called when changing tab, so don't delete # tabs until later QtCore.QTimer.singleShot(0, self.display_location) def display_coords(self): images = self.image_list.get_selected_images() if not images: self.coords.set_value(None) self.auto_location.setEnabled(False) return values = [] for image in images: value = image.metadata.latlong if value not in values: values.append(value) if len(values) > 1: self.coords.set_multiple(choices=filter(None, values)) self.auto_location.setEnabled(False) else: self.coords.set_value(values[0]) self.auto_location.setEnabled( bool(values[0]) and not self.block_timer.isActive()) def set_tab_text(self, idx): if idx == 0: text = self.tr('camera') else: text = self.tr('subject {}').format(idx) self.location_info.setTabText(idx, text) @QtCore.pyqtSlot() @catch_all def display_location(self): images = self.image_list.get_selected_images() # get required number of tabs count = 0 for image in images: if image.metadata.location_shown: count = max(count, len(image.metadata.location_shown)) count += 2 # add or remove tabs if self.location_info.currentIndex() >= count: self.location_info.setCurrentIndex(count - 1) idx = self.location_info.count() while idx < count: if not self.location_widgets: widget = LocationInfo() widget.new_value.connect(self.new_location) self.location_widgets.append(widget) self.location_info.addTab(self.location_widgets.pop(), '') self.set_tab_text(idx) idx += 1 while idx > count: idx -= 1 self.location_widgets.append(self.location_info.widget(idx)) self.location_info.removeTab(idx) # display data for idx in range(count): widget = self.location_info.widget(idx) if images: values = defaultdict(list) for image in images: location = self._get_location(image, idx) or {} for key in widget.members: value = None if key in location: value = location[key] if value not in values[key]: values[key].append(value) for key in widget.members: if len(values[key]) > 1: widget.members[key].set_multiple( choices=filter(None, values[key])) else: widget.members[key].set_value(values[key][0]) else: for key in widget.members: widget.members[key].set_value(None) @QtCore.pyqtSlot(list) @catch_all def new_selection(self, selection): self.coords.setEnabled(bool(selection)) self.location_info.setEnabled(bool(selection)) self.redraw_markers() self.display_coords() self.display_location() self.see_selection() def redraw_markers(self): if not self.map_loaded: return for info in self.marker_info.values(): info['images'] = [] for image in self.image_list.get_images(): latlong = image.metadata.latlong if not latlong: continue for info in self.marker_info.values(): if info['latlong'] == latlong: info['images'].append(image) break else: for i in range(len(self.marker_info) + 2): marker_id = i if marker_id not in self.marker_info: break self.marker_info[marker_id] = { 'images': [image], 'latlong': LatLon(latlong), 'selected': image.selected, } self.JavaScript('addMarker({:d},{!r},{!r},{:d})'.format( marker_id, latlong.lat, latlong.lon, image.selected)) for marker_id in list(self.marker_info.keys()): info = self.marker_info[marker_id] if not info['images']: self.JavaScript('delMarker({:d})'.format(marker_id)) del self.marker_info[marker_id] elif info['selected'] != any([x.selected for x in info['images']]): info['selected'] = not info['selected'] self.JavaScript('enableMarker({:d},{:d})'.format( marker_id, info['selected'])) def plot_track(self, tracks): latlngs = [] for t in tracks: latlngs.append([[x[1], x[2]] for x in t]) self.JavaScript('plotTrack({!r})'.format(latlngs)) @QtCore.pyqtSlot() @catch_all def enable_search(self): self.block_timer.stop() self.edit_box.lineEdit().setEnabled(self.map_loaded) if self.search_string: item = self.edit_box.model().item(1) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) item = self.edit_box.model().item(2) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.display_coords() def disable_search(self): self.edit_box.lineEdit().setEnabled(False) if self.search_string: item = self.edit_box.model().item(1) item.setFlags(~(Qt.ItemIsSelectable | Qt.ItemIsEnabled)) item = self.edit_box.model().item(2) item.setFlags(~(Qt.ItemIsSelectable | Qt.ItemIsEnabled)) self.auto_location.setEnabled(False) self.block_timer.start() focus = QtWidgets.QApplication.focusWidget() if focus: focus.clearFocus() def do_geocode(self, params): cache_key = params['q'] if 'bounds' in params: cache_key += params['bounds'] if cache_key in self.geocode_cache: return self.geocode_cache[cache_key] self.disable_search() params['key'] = self.search_key params['abbrv'] = '1' params['no_annotations'] = '1' lang, encoding = locale.getdefaultlocale() if lang: params['language'] = lang with Busy(): try: rsp = requests.get( 'https://api.opencagedata.com/geocode/v1/json', params=params, timeout=5) except Exception as ex: logger.error(str(ex)) return [] if rsp.status_code >= 400: logger.error('Search error %d', rsp.status_code) return [] rsp = rsp.json() status = rsp['status'] if status['code'] != 200: logger.error('Search error %d: %s', status['code'], status['message']) return [] if rsp['total_results'] < 1: logger.error('No results found') return [] rate = rsp['rate'] self.block_timer.setInterval(5000 * rate['limit'] // max(rate['remaining'], 1)) self.geocode_cache[cache_key] = rsp['results'] while len(self.geocode_cache) > 20: self.geocode_cache.popitem(last=False) return rsp['results'] def geocode(self, search_string, bounds=None): params = { 'q': search_string, 'limit': '20', } if bounds: north, east, south, west = bounds w = east - west h = north - south if min(w, h) < 10.0: lat, lon = self.map_status['centre'] north = min(lat + 5.0, 90.0) south = max(lat - 5.0, -90.0) east = lon + 5.0 west = lon - 5.0 params['bounds'] = '{!r},{!r},{!r},{!r}'.format( west, south, east, north) for result in self.do_geocode(params): yield (result['bounds']['northeast']['lat'], result['bounds']['northeast']['lng'], result['bounds']['southwest']['lat'], result['bounds']['southwest']['lng'], result['formatted']) # Map OpenCage address components to IPTC address heirarchy. There # are many possible components (user generated data) so any # unrecognised ones are put in 'sublocation'. See # https://github.com/OpenCageData/address-formatting/blob/master/conf/components.yaml address_map = { 'world_region': ('continent', ), 'country_code': ('ISO_3166-1_alpha-3', 'ISO_3166-1_alpha-2', 'country_code'), 'country_name': ('country', 'country_name'), 'province_state': ('county', 'county_code', 'local_administrative_area', 'state_district', 'state', 'state_code', 'province', 'region', 'island'), 'city': ('village', 'locality', 'hamlet', 'neighbourhood', 'city_district', 'suburb', 'city', 'town', 'postcode'), 'sublocation': ('house_number', 'street_number', 'house', 'public_building', 'building', 'water', 'road', 'pedestrian', 'path', 'residential', 'street_name', 'street', 'cycleway', 'footway', 'place'), 'ignore': ('political_union', 'road_reference', 'road_reference_intl', 'road_type', '_type'), } @QtCore.pyqtSlot() @catch_all def get_address(self): results = self.do_geocode( {'q': self.coords.get_value().replace(' ', '')}) if not results: return address = dict(results[0]['components']) if 'county_code' in address and 'county' in address: del address['county_code'] if 'state_code' in address and 'state' in address: del address['state_code'] self.new_location(self.location_info.currentWidget(), Location.from_address(address, self.address_map)) @QtCore.pyqtSlot() @catch_all def search(self, search_string=None, bounded=True): if not search_string: search_string = self.edit_box.lineEdit().text() self.edit_box.clearEditText() if not search_string: return self.search_string = search_string self.clear_search() if bounded: bounds = self.map_status['bounds'] else: bounds = None for result in self.geocode(search_string, bounds=bounds): north, east, south, west, name = result self.edit_box.addItem(name, (north, east, south, west)) self.edit_box.set_dropdown_width() self.edit_box.showPopup() def clear_search(self): self.edit_box.clear() self.edit_box.addItem('') if self.search_string: self.edit_box.addItem(translate('PhotiniMap', '<widen search>')) self.edit_box.addItem(translate('PhotiniMap', '<repeat search>')) @QtCore.pyqtSlot(int) @catch_all def goto_search_result(self, idx): self.edit_box.setCurrentIndex(0) self.edit_box.clearFocus() if idx == 0: return if self.search_string and idx == 1: # widen search self.search(search_string=self.search_string, bounded=False) return if self.search_string and idx == 2: # repeat search self.search(search_string=self.search_string) return view = self.edit_box.itemData(idx) if view[-1] is None: self.JavaScript('setView({},{},{})'.format( view[0], view[1], self.map_status['zoom'])) else: self.JavaScript('adjustBounds({},{},{},{})'.format(*view)) @catch_all def marker_click(self, marker_id): self.image_list.select_images(self.marker_info[marker_id]['images']) @catch_all def marker_drag(self, lat, lng): self.coords.set_value('{:.6f}, {:.6f}'.format(lat, lng)) @catch_all def marker_drag_end(self, lat, lng, marker_id): info = self.marker_info[marker_id] for image in info['images']: image.metadata.latlong = lat, lng info['latlong'] = LatLon((lat, lng)) self.display_coords() def JavaScript(self, command): if self.map_loaded: self.map.page().do_java_script(command)
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, *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 = 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-IIM metadata'), self.write_iptc) # show IPTC-IIM length limits length_warning = self.config_store.get('files', 'length_warning', True) self.length_warning = QtWidgets.QCheckBox( self.tr('Show IPTC-IIM length limits')) self.length_warning.setChecked(length_warning) panel.layout().addRow('', self.length_warning) # sidecar files if_mode = self.config_store.get('files', 'image', True) sc_mode = self.config_store.get('files', 'sidecar', 'auto') if not if_mode: sc_mode = 'always' button_group = QtWidgets.QButtonGroup(parent=self) self.sc_always = QtWidgets.QRadioButton(self.tr('Always create')) button_group.addButton(self.sc_always) 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')) button_group.addButton(self.sc_auto) 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')) button_group.addButton(self.sc_delete) 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 = self.config_store.get('files', 'preserve_timestamps', 'now') if isinstance(keep_time, bool): # old config format keep_time = ('now', 'keep')[keep_time] button_group = QtWidgets.QButtonGroup(parent=self) self.keep_time = QtWidgets.QRadioButton(self.tr('Keep original')) button_group.addButton(self.keep_time) self.keep_time.setChecked(keep_time == 'keep') panel.layout().addRow(self.tr('File timestamps'), self.keep_time) self.time_taken = QtWidgets.QRadioButton( self.tr('Set to when photo was taken')) button_group.addButton(self.time_taken) self.time_taken.setChecked(keep_time == 'taken') panel.layout().addRow('', self.time_taken) button = QtWidgets.QRadioButton(self.tr('Set to when file is saved')) button_group.addButton(button) button.setChecked(keep_time == 'now') panel.layout().addRow('', button) # add panel to scroll area after its size is known scroll_area.setWidget(panel)