def show_album(self, album): if album: self.albums.setCurrentIndex(self.albums.findData(album.id.text)) self.widgets['description'].set_value(album.summary.text) self.widgets['location'].setText(album.location.text) self.widgets['access'].setCurrentIndex( self.widgets['access'].findData(album.access.text)) self.widgets['timestamp'].setDateTime( QtCore.QDateTime.fromTime_t(int(album.timestamp.text) // 1000)) if album.group.thumbnail is not None: QtWidgets.QApplication.processEvents() with Busy(): url = album.group.thumbnail[0].get('url') image = QtGui.QPixmap(160, 160) try: image.loadFromData(urlopen(url).read()) except HTTPError as ex: paint = QtGui.QPainter(image) paint.fillRect(image.rect(), Qt.black) paint.setPen(Qt.white) paint.drawText(image.rect(), Qt.AlignLeft | Qt.TextWordWrap, str(ex)) paint.end() self.album_thumb.setPixmap(image) else: self.widgets['description'].set_value(None) self.widgets['location'].clear() self.widgets['timestamp'].clear() self.album_thumb.clear()
def __init__(self, *arg, **kw): super(BingMap, self).__init__(*arg, **kw) if self.app.test_mode: self.drag_icon = QtGui.QPixmap( os.path.join(self.script_dir, 'grey_marker_v8.png')) self.map.settings().setAttribute( QtWebKit.QWebSettings.LocalContentCanAccessRemoteUrls, True) self.map.settings().setAttribute( QtWebKit.QWebSettings.LocalContentCanAccessFileUrls, True)
def initialize_finished(self): QtWidgets.QApplication.restoreOverrideCursor() self.map_loaded = True self.edit_box.setEnabled(True) self.map.setAcceptDrops(True) drag_icon = QtGui.QPixmap( os.path.join(self.script_dir, '../map_pin_grey.png')) drag_hotspot = 11, 35 self.image_list.set_drag_to_map(drag_icon, drag_hotspot) self.redraw_markers() self.display_coords()
def show_user(self, name, picture): if name: self.user_name.setText( self.tr('Connected to {0} on {1}').format( name, self.service_name)) else: self.user_name.setText( self.tr('Not connected to {}').format(self.service_name)) pixmap = QtGui.QPixmap() if picture: pixmap.loadFromData(picture) self.user_photo.setPixmap(pixmap)
def load_thumbnail(self): pixmap = QtGui.QPixmap() thumb = self.metadata.thumbnail if thumb: pixmap.loadFromData(thumb.data) if pixmap.isNull(): self.image.setText(self.tr('No\nthumbnail\nin file')) return pixmap = self.transform(pixmap, self.metadata.orientation) self.image.setPixmap( pixmap.scaled(self.thumb_size, self.thumb_size, Qt.KeepAspectRatio, Qt.SmoothTransformation))
def show_user(self, name, picture): if name: self.user_name.setText(translate( 'UploaderTabsAll', 'Logged in as {0} on {1}').format(name, self.service_name)) else: self.user_name.setText(translate( 'UploaderTabsAll', 'Not logged in to {}').format(self.service_name)) pixmap = QtGui.QPixmap() if picture: pixmap.loadFromData(picture) self.user_photo.setPixmap(pixmap)
def mouseMoveEvent(self, event): if not self.image_list.drag_icon: return if qt_version_info >= (6, 0): pos = event.position() else: pos = event.pos() if ((pos - self.drag_start_pos).manhattanLength() < QtWidgets.QApplication.startDragDistance()): return if not self.get_selected(): # user has started dragging an unselected image self.image_list.select_image(self) paths = [] for image in self.image_list.get_selected_images(): paths.append(image.path) if not paths: return drag = QtGui.QDrag(self) # construct icon count = min(len(paths), 8) src_icon = self.image_list.drag_icon src_w = src_icon.width() src_h = src_icon.height() margin = (count - 1) * 4 if count == 1: icon = src_icon else: icon = QtGui.QPixmap(src_w + margin, src_h + margin) icon.fill(Qt.transparent) try: paint = QtGui.QPainter(icon) for i in range(count): paint.drawPixmap(QtCore.QPoint(margin - (i * 4), i * 4), src_icon) finally: del paint drag.setPixmap(icon) if self.image_list.drag_hotspot: x, y = self.image_list.drag_hotspot else: x, y = src_w // 2, src_h drag.setHotSpot(QtCore.QPoint(x, y + margin)) mimeData = QtCore.QMimeData() mimeData.setData(DRAG_MIMETYPE, repr(paths).encode('utf-8')) drag.setMimeData(mimeData) if qt_version_info >= (6, 0): drag.exec(Qt.CopyAction) else: drag.exec_(Qt.CopyAction)
def show_user(self, name, picture): if name: self.user_name.setText( self.tr('Connected to {0} on {1}').format( name, self.service_name)) else: self.user_name.setText( self.tr('Not connected to {}').format(self.service_name)) pixmap = QtGui.QPixmap() if picture: try: pixmap.loadFromData(urlopen(picture).read()) except URLError as ex: self.logger.error('cannot read %s: %s', picture, str(ex)) self.user_photo.setPixmap(pixmap)
def __init__(self, *arg, **kw): self.upload_config = FacebookUploadConfig() super(FacebookUploader, self).__init__(self.upload_config, *arg, **kw) self.upload_config.new_album.connect(self.new_album) self.upload_config.select_album.connect(self.select_album) self.service_name = self.tr('Facebook') self.image_types = { 'accepted': ('image/jpeg', 'image/png'), 'rejected': ('image/x-portable-anymap', 'image/x-dcraw'), } self.login_popup = None # add Facebook icon to connect button icon_file = pkg_resources.resource_filename('photini', 'data/facebook_logo.png') self.user_connect.setIcon(QtGui.QIcon(QtGui.QPixmap(icon_file)))
def get_album_thumb(self, album): if album.group.thumbnail is None: return None image = QtGui.QPixmap(160, 160) try: resp = self._check_response( self.session.get(album.group.thumbnail[0].get('url'))) image.loadFromData(resp.content) except Exception as ex: paint = QtGui.QPainter(image) paint.fillRect(image.rect(), Qt.black) paint.setPen(Qt.white) paint.drawText(image.rect(), Qt.AlignLeft | Qt.TextWordWrap, str(ex)) paint.end() return image
def show_album(self, album, picture): if 'description' in album: self.widgets['album_description'].setPlainText( album['description']) else: self.widgets['album_description'].clear() if 'location' in album: self.widgets['album_location'].setText(album['location']) else: self.widgets['album_location'].clear() pixmap = QtGui.QPixmap() if picture: try: rsp = requests.get(picture) pixmap.loadFromData(rsp.content) except Exception as ex: self.logger.error('cannot read %s: %s', picture, str(ex)) self.widgets['album_thumb'].setPixmap(pixmap)
def show_album(self, album, picture): if 'description' in album: self.widgets['album_description'].setPlainText( album['description']) else: self.widgets['album_description'].clear() if 'location' in album: self.widgets['album_location'].setText(album['location']) else: self.widgets['album_location'].clear() pixmap = QtGui.QPixmap() if picture: rsp = requests.get(picture) if rsp.status_code == 200: pixmap.loadFromData(rsp.content) else: logger.error('HTTP error %d (%s)', rsp.status_code, picture) self.widgets['album_thumb'].setPixmap(pixmap)
def write(self, handler, tag): if handler.is_xmp_tag(tag): data = self.data if self.fmt != 'JPEG': pixmap = QtGui.QPixmap() pixmap.loadFromData(data) buf = QtCore.QBuffer() buf.open(QtCore.QIODevice.WriteOnly) pixmap.save(buf, 'JPEG') data = buf.data().data() w = pixmap.width() h = pixmap.height() else: w, h = self.size() data = codecs.encode(data, 'base64_codec') if not six.PY2: data = data.decode('ascii') handler.set_string(tag, (data, 'JPEG', str(w), str(h))) elif handler.is_exif_tag(tag): handler.set_exif_thumbnail_from_buffer(self.data)
def mouseMoveEvent(self, event): if not self.image_list.drag_icon: return if ((event.pos() - self.drag_start_pos).manhattanLength() < QtWidgets.QApplication.startDragDistance()): return paths = [] for image in self.image_list.get_selected_images(): paths.append(image.path) if not paths: return drag = QtGui.QDrag(self) # construct icon count = min(len(paths), 8) src_icon = self.image_list.drag_icon src_w = src_icon.width() src_h = src_icon.height() margin = (count - 1) * 4 if count == 1: icon = src_icon else: icon = QtGui.QPixmap(src_w + margin, src_h + margin) icon.fill(Qt.transparent) with QtGui.QPainter(icon) as paint: for i in range(count): paint.drawPixmap( QtCore.QPoint(margin - (i * 4), i * 4), src_icon) drag.setPixmap(icon) if src_h == src_w: # round marker used in Bing maps version 8 drag.setHotSpot(QtCore.QPoint(src_w // 2, (src_h // 2) + margin)) else: drag.setHotSpot(QtCore.QPoint(src_w // 2, src_h + margin)) mimeData = QtCore.QMimeData() mimeData.setData(DRAG_MIMETYPE, repr(paths).encode('utf-8')) drag.setMimeData(mimeData) dropAction = drag.exec_(Qt.CopyAction)
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, 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 _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 __init__(self, options, initial_files): super(MainWindow, self).__init__() self.setWindowTitle( translate('MenuBar', "Photini photo metadata editor")) pixmap = QtGui.QPixmap() pixmap.loadFromData( pkg_resources.resource_string('photini', 'data/icons/photini_48.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 = urllib.request.getproxies() if 'http' in proxies: parsed = urllib.parse.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.open_cage = OpenCage(parent=self) if GpxImporter: self.app.gpx_importer = GpxImporter(parent=self) else: self.app.gpx_importer = None self.app.options = options # initialise metadata handler ImageMetadata.initialise(self.app.config_store, options.verbose) # restore size and state size = self.width(), self.height() self.resize(*self.app.config_store.get('main_window', 'size', size)) window_state = self.app.config_store.get('main_window', 'state', 0) full_screen = window_state & int(Qt.WindowMaximized | Qt.WindowFullScreen) if full_screen: self.setWindowState(self.windowState() | full_screen) # image selector self.image_list = ImageList() self.image_list.selection_changed.connect(self.new_selection) self.image_list.image_list_changed.connect(self.new_image_list) self.image_list.new_metadata.connect(self.new_metadata) # start instance server instance_server = InstanceServer(parent=self) instance_server.new_files.connect(self.image_list.open_file_list, Qt.QueuedConnection) # 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', 'address': 'photini.address', '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 = [] default_modules = [ 'photini.descriptive', 'photini.ownership', 'photini.technical', 'photini.googlemap', 'photini.bingmap', 'photini.mapboxmap', 'photini.openstreetmap', 'photini.address', 'photini.flickr', 'photini.googlephotos', 'photini.importer' ] modules = self.app.config_store.get('tabs', 'modules', default_modules) for n, module in enumerate(default_modules): if module not in modules: modules = list(modules) modules.insert(n, module) self.app.config_store.set('tabs', 'modules', 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(translate('MenuBar', 'File')) action = file_menu.addAction(translate('MenuBar', 'Open files')) action.setShortcuts(QtGui.QKeySequence.Open) action.triggered.connect(self.image_list.open_files) self.save_action = file_menu.addAction( translate('MenuBar', 'Save changes')) self.save_action.setShortcuts(QtGui.QKeySequence.Save) self.save_action.setEnabled(False) self.save_action.triggered.connect(self.image_list.save_files) self.fix_thumbs_action = file_menu.addAction( translate('MenuBar', 'Fix missing thumbnails')) self.fix_thumbs_action.setEnabled(False) self.fix_thumbs_action.triggered.connect( self.image_list.fix_missing_thumbs) action = file_menu.addAction(translate('MenuBar', 'Close all files')) action.triggered.connect(self.image_list.close_all_files) sep = file_menu.addAction(translate('MenuBar', 'Selected images')) sep.setSeparator(True) self.selected_actions = self.image_list.add_selected_actions(file_menu) file_menu.addSeparator() action = file_menu.addAction(translate('MenuBar', 'Quit')) action.setShortcuts( [QtGui.QKeySequence.Quit, QtGui.QKeySequence.Close]) action.triggered.connect( QtWidgets.QApplication.instance().closeAllWindows) # options menu options_menu = self.menuBar().addMenu(translate('MenuBar', 'Options')) action = options_menu.addAction(translate('MenuBar', 'Settings')) action.triggered.connect(self.edit_settings) options_menu.addSeparator() for tab in self.tab_list: if tab['class']: name = tab['name'].replace('&', '') else: name = tab['module'] tab['action'] = options_menu.addAction(name) tab['action'].setCheckable(True) if tab['class']: tab['action'].setChecked( self.app.config_store.get('tabs', tab['module'], True)) else: tab['action'].setEnabled(False) tab['action'].triggered.connect(self.add_tabs) # spelling menu languages = self.app.spell_check.available_languages() spelling_menu = self.menuBar().addMenu(translate( 'MenuBar', 'Spelling')) action = spelling_menu.addAction( translate('MenuBar', 'Enable spell check')) action.setEnabled(languages is not None) action.setCheckable(True) action.setChecked(self.app.spell_check.enabled) action.toggled.connect(self.app.spell_check.enable) current_language = self.app.spell_check.current_language() if languages: language_group = QtGui2.QActionGroup(self) for language in sorted(languages): dict_list = languages[language] if len(dict_list) == 1: language_menu = spelling_menu else: language_menu = spelling_menu.addMenu(language) for country, code in dict_list: if country: name = '{}: {}'.format(language, country) else: name = language action = language_menu.addAction(name) action.setCheckable(True) action.setChecked(code == current_language) action.setData(code) action.setActionGroup(language_group) language_group.triggered.connect(self.set_language) # help menu help_menu = self.menuBar().addMenu(translate('MenuBar', 'Help')) action = help_menu.addAction(translate('MenuBar', 'About Photini')) action.triggered.connect(self.about) action = help_menu.addAction(translate('MenuBar', 'Check for update')) action.triggered.connect(self.check_update) help_menu.addSeparator() action = help_menu.addAction( translate('MenuBar', 'Photini documentation')) action.triggered.connect(self.open_docs) # 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( self.app.config_store.get('main_window', 'split', 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)
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, 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 __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 __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 size(self): if self.w and self.h: return self.w, self.h pixmap = QtGui.QPixmap() pixmap.loadFromData(self.data) return pixmap.width(), pixmap.height()