def __init__(self, options, initial_files): super(MainWindow, self).__init__() self.setWindowTitle(self.tr("Photini photo metadata editor")) pixmap = QtGui.QPixmap() pixmap.loadFromData( pkg_resources.resource_string('photini', 'data/icons/48/photini.png')) icon = QtGui.QIcon(pixmap) self.setWindowIcon(icon) self.selection = list() # logger window self.loggerwindow = LoggerWindow(options.verbose) self.loggerwindow.setWindowIcon(icon) self.logger = logging.getLogger(self.__class__.__name__) # set network proxy proxies = getproxies() if 'http' in proxies: parsed = urlparse(proxies['http']) QNetworkProxy.setApplicationProxy( QNetworkProxy(QNetworkProxy.HttpProxy, parsed.hostname, parsed.port)) # create shared global objects self.app = QtWidgets.QApplication.instance() self.app.config_store = ConfigStore('editor', parent=self) self.app.spell_check = SpellCheck(parent=self) self.app.test_mode = options.test # set debug mode if self.app.test_mode: debug_metadata() # restore size size = self.width(), self.height() self.resize( *eval(self.app.config_store.get('main_window', 'size', str(size)))) # image selector self.image_list = ImageList() self.image_list.selection_changed.connect(self.new_selection) self.image_list.new_metadata.connect(self.new_metadata) # prepare list of tabs and associated stuff self.tab_list = ( { 'name': self.tr('&Descriptive metadata'), 'key': 'descriptive_metadata', 'class': Descriptive }, { 'name': self.tr('&Technical metadata'), 'key': 'technical_metadata', 'class': Technical }, { 'name': self.tr('Map (&Google)'), 'key': 'map_google', 'class': GoogleMap }, { 'name': self.tr('Map (&Bing)'), 'key': 'map_bing', 'class': BingMap }, { 'name': self.tr('Map (&OSM)'), 'key': 'map_osm', 'class': OpenStreetMap }, { 'name': self.tr('&Flickr upload'), 'key': 'flickr_upload', 'class': FlickrUploader }, { 'name': self.tr('Google &Photos upload'), 'key': 'picasa_upload', 'class': PicasaUploader }, { 'name': self.tr('Faceboo&k upload'), 'key': 'facebook_upload', 'class': FacebookUploader }, { 'name': self.tr('&Import photos'), 'key': 'import_photos', 'class': Importer }, ) # file menu file_menu = self.menuBar().addMenu(self.tr('File')) open_action = QtWidgets.QAction(self.tr('Open images'), self) open_action.setShortcuts(QtGui.QKeySequence.Open) open_action.triggered.connect(self.image_list.open_files) file_menu.addAction(open_action) self.save_action = QtWidgets.QAction( self.tr('Save images with new data'), self) self.save_action.setShortcuts(QtGui.QKeySequence.Save) self.save_action.setEnabled(False) self.save_action.triggered.connect(self.image_list.save_files) file_menu.addAction(self.save_action) self.close_action = QtWidgets.QAction(self.tr('Close selected images'), self) self.close_action.setEnabled(False) self.close_action.triggered.connect(self.close_files) file_menu.addAction(self.close_action) close_all_action = QtWidgets.QAction(self.tr('Close all images'), self) close_all_action.triggered.connect(self.close_all_files) file_menu.addAction(close_all_action) file_menu.addSeparator() quit_action = QtWidgets.QAction(self.tr('Quit'), self) quit_action.setShortcuts( [QtGui.QKeySequence.Quit, QtGui.QKeySequence.Close]) quit_action.triggered.connect( QtWidgets.QApplication.instance().closeAllWindows) file_menu.addAction(quit_action) # options menu options_menu = self.menuBar().addMenu(self.tr('Options')) settings_action = QtWidgets.QAction(self.tr('Settings'), self) settings_action.triggered.connect(self.edit_settings) options_menu.addAction(settings_action) options_menu.addSeparator() for tab in self.tab_list: name = tab['name'].replace('&', '') tab['action'] = QtWidgets.QAction(name, self) tab['action'].setCheckable(True) if tab['class']: tab['action'].setChecked( eval(self.app.config_store.get('tabs', tab['key'], 'True'))) else: tab['action'].setEnabled(False) tab['action'].triggered.connect(self.add_tabs) options_menu.addAction(tab['action']) # spelling menu languages = self.app.spell_check.available_languages() spelling_menu = self.menuBar().addMenu(self.tr('Spelling')) enable_action = QtWidgets.QAction(self.tr('Enable spell check'), self) enable_action.setEnabled(bool(languages)) enable_action.setCheckable(True) enable_action.setChecked(self.app.spell_check.enabled) enable_action.toggled.connect(self.app.spell_check.enable) spelling_menu.addAction(enable_action) language_menu = QtWidgets.QMenu(self.tr('Choose language'), self) language_menu.setEnabled(bool(languages)) language_group = QtWidgets.QActionGroup(self) current_language = self.app.spell_check.current_language() for tag in languages: language_action = QtWidgets.QAction(tag, self) language_action.setCheckable(True) language_action.setChecked(tag == current_language) language_action.setActionGroup(language_group) language_menu.addAction(language_action) language_group.triggered.connect(self.app.spell_check.set_language) spelling_menu.addMenu(language_menu) # help menu help_menu = self.menuBar().addMenu(self.tr('Help')) about_action = QtWidgets.QAction(self.tr('About Photini'), self) about_action.triggered.connect(self.about) help_menu.addAction(about_action) help_menu.addSeparator() help_action = QtWidgets.QAction(self.tr('Photini documentation'), self) help_action.triggered.connect(self.open_docs) help_menu.addAction(help_action) # main application area self.central_widget = QtWidgets.QSplitter() self.central_widget.setOrientation(Qt.Vertical) self.central_widget.setChildrenCollapsible(False) self.tabs = QtWidgets.QTabWidget() self.tabs.setTabBar(QTabBar()) self.tabs.setElideMode(Qt.ElideRight) self.tabs.currentChanged.connect(self.new_tab) self.add_tabs() self.central_widget.addWidget(self.tabs) self.central_widget.addWidget(self.image_list) size = self.central_widget.sizes() self.central_widget.setSizes( eval(self.app.config_store.get('main_window', 'split', str(size)))) self.central_widget.splitterMoved.connect(self.new_split) self.setCentralWidget(self.central_widget) # open files given on command line, after GUI is displayed self.initial_files = initial_files if self.initial_files: QtCore.QTimer.singleShot(0, self.open_initial_files)
def __init__(self, options, initial_files): super(MainWindow, self).__init__() self.setWindowTitle(self.tr("Photini photo metadata editor")) pixmap = QtGui.QPixmap() pixmap.loadFromData(pkg_resources.resource_string( 'photini', 'data/icons/48/photini.png')) icon = QtGui.QIcon(pixmap) self.setWindowIcon(icon) self.selection = list() # logger window self.loggerwindow = LoggerWindow(options.verbose) self.loggerwindow.setWindowIcon(icon) # set network proxy proxies = getproxies() if 'http' in proxies: parsed = urlparse(proxies['http']) QNetworkProxy.setApplicationProxy(QNetworkProxy( QNetworkProxy.HttpProxy, parsed.hostname, parsed.port)) # create shared global objects self.app = QtWidgets.QApplication.instance() self.app.config_store = ConfigStore('editor', parent=self) self.app.spell_check = SpellCheck(parent=self) self.app.test_mode = options.test # restore size size = self.width(), self.height() self.resize(*eval( self.app.config_store.get('main_window', 'size', str(size)))) # image selector self.image_list = ImageList() self.image_list.selection_changed.connect(self.new_selection) self.image_list.new_metadata.connect(self.new_metadata) # update config file if self.app.config_store.config.has_section('tabs'): conv = { 'descriptive_metadata': 'photini.descriptive', 'technical_metadata' : 'photini.technical', 'map_google' : 'photini.googlemap', 'map_bing' : 'photini.bingmap', 'map_mapbox' : 'photini.mapboxmap', 'map_osm' : 'photini.openstreetmap', 'flickr_upload' : 'photini.flickr', 'import_photos' : 'photini.importer', } for key in self.app.config_store.config.options('tabs'): if key in conv: self.app.config_store.set( 'tabs', conv[key], self.app.config_store.get('tabs', key)) self.app.config_store.config.remove_option('tabs', key) # prepare list of tabs and associated stuff self.tab_list = [] modules = ('photini.descriptive', 'photini.technical', 'photini.googlemap', 'photini.bingmap', 'photini.mapboxmap', 'photini.openstreetmap', 'photini.flickr', 'photini.googlephotos', 'photini.importer') modules = eval(self.app.config_store.get( 'tabs', 'modules', pprint.pformat(modules))) for module in modules: tab = {'module': module} try: mod = importlib.import_module(tab['module']) tab['class'] = mod.TabWidget tab['name'] = tab['class'].tab_name() except ImportError as ex: print(str(ex)) tab['class'] = None self.tab_list.append(tab) # file menu file_menu = self.menuBar().addMenu(self.tr('File')) open_action = QtWidgets.QAction(self.tr('Open images'), self) open_action.setShortcuts(QtGui.QKeySequence.Open) open_action.triggered.connect(self.image_list.open_files) file_menu.addAction(open_action) self.save_action = QtWidgets.QAction( self.tr('Save images with new data'), self) self.save_action.setShortcuts(QtGui.QKeySequence.Save) self.save_action.setEnabled(False) self.save_action.triggered.connect(self.image_list.save_files) file_menu.addAction(self.save_action) self.close_action = QtWidgets.QAction( self.tr('Close selected images'), self) self.close_action.setEnabled(False) self.close_action.triggered.connect(self.close_files) file_menu.addAction(self.close_action) close_all_action = QtWidgets.QAction(self.tr('Close all images'), self) close_all_action.triggered.connect(self.close_all_files) file_menu.addAction(close_all_action) if GpxImporter: file_menu.addSeparator() self.import_gpx_action = QtWidgets.QAction( self.tr('Import GPX file'), self) self.import_gpx_action.triggered.connect(self.import_pgx_file) file_menu.addAction(self.import_gpx_action) else: self.import_gpx_action = None file_menu.addSeparator() quit_action = QtWidgets.QAction(self.tr('Quit'), self) quit_action.setShortcuts( [QtGui.QKeySequence.Quit, QtGui.QKeySequence.Close]) quit_action.triggered.connect( QtWidgets.QApplication.instance().closeAllWindows) file_menu.addAction(quit_action) # options menu options_menu = self.menuBar().addMenu(self.tr('Options')) settings_action = QtWidgets.QAction(self.tr('Settings'), self) settings_action.triggered.connect(self.edit_settings) options_menu.addAction(settings_action) options_menu.addSeparator() for tab in self.tab_list: if tab['class']: name = tab['name'].replace('&', '') else: name = tab['module'] tab['action'] = QtWidgets.QAction(name, self) tab['action'].setCheckable(True) if tab['class']: tab['action'].setChecked(eval( self.app.config_store.get('tabs', tab['module'], 'True'))) else: tab['action'].setEnabled(False) tab['action'].triggered.connect(self.add_tabs) options_menu.addAction(tab['action']) # spelling menu languages = self.app.spell_check.available_languages() spelling_menu = self.menuBar().addMenu(self.tr('Spelling')) enable_action = QtWidgets.QAction(self.tr('Enable spell check'), self) enable_action.setEnabled(languages is not None) enable_action.setCheckable(True) enable_action.setChecked(self.app.spell_check.enabled) enable_action.toggled.connect(self.app.spell_check.enable) spelling_menu.addAction(enable_action) language_menu = QtWidgets.QMenu(self.tr('Choose language'), self) language_menu.setEnabled(languages is not None) current_language = self.app.spell_check.current_language() if languages: language_group = QtWidgets.QActionGroup(self) for name, code in languages: if name != code: name = code + ': ' + name language_action = QtWidgets.QAction(name, self) language_action.setCheckable(True) language_action.setChecked(code == current_language) language_action.setData(code) language_action.setActionGroup(language_group) language_menu.addAction(language_action) language_group.triggered.connect(self.set_language) else: language_action = QtWidgets.QAction( self.tr('No dictionary installed'), self) language_action.setEnabled(False) language_menu.addAction(language_action) spelling_menu.addMenu(language_menu) # help menu help_menu = self.menuBar().addMenu(self.tr('Help')) about_action = QtWidgets.QAction(self.tr('About Photini'), self) about_action.triggered.connect(self.about) help_menu.addAction(about_action) help_menu.addSeparator() help_action = QtWidgets.QAction(self.tr('Photini documentation'), self) help_action.triggered.connect(self.open_docs) help_menu.addAction(help_action) # main application area self.central_widget = QtWidgets.QSplitter() self.central_widget.setOrientation(Qt.Vertical) self.central_widget.setChildrenCollapsible(False) self.tabs = QtWidgets.QTabWidget() self.tabs.setTabBar(QTabBar()) self.tabs.setElideMode(Qt.ElideRight) self.tabs.currentChanged.connect(self.new_tab) self.add_tabs(False) self.central_widget.addWidget(self.tabs) self.central_widget.addWidget(self.image_list) size = self.central_widget.sizes() self.central_widget.setSizes(eval( self.app.config_store.get('main_window', 'split', str(size)))) self.central_widget.splitterMoved.connect(self.new_split) self.setCentralWidget(self.central_widget) # open files given on command line, after GUI is displayed self.initial_files = initial_files if self.initial_files: QtCore.QTimer.singleShot(0, self.open_initial_files)
class MainWindow(QtWidgets.QMainWindow): 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) @QtCore.pyqtSlot() def open_initial_files(self): self.image_list.open_file_list(self.initial_files) @QtCore.pyqtSlot() def add_tabs(self): was_blocked = self.tabs.blockSignals(True) current = self.tabs.currentWidget() self.tabs.clear() for tab in self.tab_list: if not tab['class']: self.app.config_store.set('tabs', tab['key'], 'True') continue use_tab = tab['action'].isChecked() self.app.config_store.set('tabs', tab['key'], str(use_tab)) if not use_tab: continue if 'object' not in tab: tab['object'] = tab['class'](self.image_list) self.tabs.addTab(tab['object'], tab['name']) self.tabs.blockSignals(was_blocked) if current: self.tabs.setCurrentWidget(current) self.new_tab(-1) @QtCore.pyqtSlot() def open_docs(self): webbrowser.open_new('http://photini.readthedocs.io/') @QtCore.pyqtSlot() def close_files(self): self._close_files(False) @QtCore.pyqtSlot() def close_all_files(self): self._close_files(True) def _close_files(self, all_files): if self.image_list.unsaved_files_dialog(all_files=all_files): self.image_list.close_files(all_files) def closeEvent(self, event): for n in range(self.tabs.count()): if self.tabs.widget(n).do_not_close(): event.ignore() return self.image_list.unsaved_files_dialog(all_files=True, with_cancel=False) super(MainWindow, self).closeEvent(event) @QtCore.pyqtSlot() def edit_settings(self): dialog = EditSettings(self) dialog.exec_() self.tabs.currentWidget().refresh() @QtCore.pyqtSlot() def about(self): text = self.tr(""" <table width="100%"><tr> <td align="center" width="70%"> <h1>Photini</h1> <h3>version {0}</h3> <h4>build {1}</h4> </td> <td align="center"><img src="{2}" /></td> </tr></table> <p>© Jim Easterbrook <a href="mailto:[email protected]"> [email protected]</a><br /><br /> An easy to use digital photograph metadata editor.<br /> Open source package available from <a href="https://github.com/jim-easterbrook/Photini"> github.com/jim-easterbrook/Photini</a>.</p> <p>This program is released with a GNU General Public License. For details click the 'show details' button.</p> """).format( __version__, build, pkg_resources.resource_filename('photini', 'data/icons/128/photini.png')) dialog = QtWidgets.QMessageBox(self) dialog.setWindowTitle(self.tr('Photini: about')) dialog.setText(text) licence = pkg_resources.resource_string('photini', 'data/LICENSE.txt') dialog.setDetailedText(licence.decode('utf-8')) dialog.exec_() @QtCore.pyqtSlot(int, int) def new_split(self, pos, index): self.app.config_store.set('main_window', 'split', str(self.central_widget.sizes())) @QtCore.pyqtSlot(int) def new_tab(self, index): current = self.tabs.currentWidget() if current: self.image_list.set_drag_to_map(None) current.refresh() self.image_list.emit_selection() @QtCore.pyqtSlot(list) def new_selection(self, selection): self.close_action.setEnabled(len(selection) > 0) self.tabs.currentWidget().new_selection(selection) @QtCore.pyqtSlot(bool) def new_metadata(self, unsaved_data): self.save_action.setEnabled(unsaved_data) def resizeEvent(self, event): size = self.width(), self.height() self.app.config_store.set('main_window', 'size', str(size))
class MainWindow(QtGui.QMainWindow): def __init__(self, verbose): QtGui.QMainWindow.__init__(self) self.setWindowTitle("Photini photo metadata editor") self.selection = list() # logger window self.loggerwindow = LoggerWindow(verbose) self.logger = logging.getLogger(self.__class__.__name__) # config store self.config_store = ConfigStore() # set network proxy proxies = urllib2.getproxies() if 'http' in proxies: scheme, host, port = proxies['http'].split(':') QNetworkProxy.setApplicationProxy( QNetworkProxy(QNetworkProxy.HttpProxy, host[2:], int(port))) # restore size size = self.width(), self.height() self.resize(*eval( self.config_store.get('main_window', 'size', str(size)))) # image selector self.image_list = ImageList(self.config_store) 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' : '&Descriptive metadata', 'class' : Descriptive}, {'name' : '&Technical metadata', 'class' : Technical}, {'name' : 'Map (&Google)', 'class' : GoogleMap}, {'name' : 'Map (&Bing)', 'class' : BingMap}, {'name' : 'Map (&OSM)', 'class' : OpenStreetMap}, {'name' : '&Flickr uploader', 'class' : FlickrUploader}, {'name' : '&Picasa uploader', 'class' : PicasaUploader}, ) for tab in self.tab_list: tab['key'] = tab['name'].replace('&', '').replace(' ', '_') tab['key'] = tab['key'].replace('(', '').replace(')', '').lower() if tab['class']: tab['object'] = tab['class'](self.config_store, self.image_list) else: tab['object'] = None # file menu file_menu = self.menuBar().addMenu('File') open_action = QtGui.QAction('Open images', self) open_action.setShortcuts(['Ctrl+O']) open_action.triggered.connect(self.image_list.open_files) file_menu.addAction(open_action) self.save_action = QtGui.QAction('Save images with new data', self) self.save_action.setShortcuts(['Ctrl+S']) self.save_action.setEnabled(False) self.save_action.triggered.connect(self.image_list.save_files) file_menu.addAction(self.save_action) self.close_action = QtGui.QAction('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 = QtGui.QAction('Close all images', self) close_all_action.triggered.connect(self.close_all_files) file_menu.addAction(close_all_action) file_menu.addSeparator() quit_action = QtGui.QAction('Quit', self) quit_action.setShortcuts(['Ctrl+Q', 'Ctrl+W']) quit_action.triggered.connect(QtGui.qApp.closeAllWindows) file_menu.addAction(quit_action) # options menu options_menu = self.menuBar().addMenu('Options') settings_action = QtGui.QAction('Settings', self) settings_action.triggered.connect(self.edit_settings) options_menu.addAction(settings_action) options_menu.addSeparator() for tab in self.tab_list: tab['action'] = QtGui.QAction(tab['name'].replace('&', ''), self) tab['action'].setCheckable(True) if tab['class']: tab['action'].setChecked( eval(self.config_store.get('tabs', tab['key'], 'True'))) else: tab['action'].setEnabled(False) tab['action'].triggered.connect(self.add_tabs) options_menu.addAction(tab['action']) # help menu help_menu = self.menuBar().addMenu('Help') about_action = QtGui.QAction('About Photini', self) about_action.triggered.connect(self.about) help_menu.addAction(about_action) help_menu.addSeparator() help_action = QtGui.QAction('Photini documentation', self) help_action.triggered.connect(self.open_docs) help_menu.addAction(help_action) # main application area self.central_widget = QtGui.QSplitter() self.central_widget.setOrientation(Qt.Vertical) self.central_widget.setChildrenCollapsible(False) self.tabs = QtGui.QTabWidget() self.add_tabs() self.tabs.currentChanged.connect(self.new_tab) self.central_widget.addWidget(self.tabs) self.central_widget.addWidget(self.image_list) size = self.central_widget.sizes() self.central_widget.setSizes(eval( self.config_store.get('main_window', 'split', str(size)))) self.central_widget.splitterMoved.connect(self.new_split) self.setCentralWidget(self.central_widget) def add_tabs(self): current = self.tabs.currentWidget() self.tabs.clear() for tab in self.tab_list: use_tab = tab['action'].isChecked() self.config_store.set('tabs', tab['key'], str(use_tab)) if tab['object'] and use_tab: self.tabs.addTab(tab['object'], tab['name']) if current: self.tabs.setCurrentWidget(current) def open_docs(self): webbrowser.open_new('http://jim-easterbrook.github.com/Photini/') def close_files(self): self._close_files(False) def close_all_files(self): self._close_files(True) def _close_files(self, all_files): if self.image_list.unsaved_files_dialog(all_files=all_files): self.image_list.close_files(all_files) def closeEvent(self, event): self.image_list.unsaved_files_dialog(with_cancel=False) QtGui.QMainWindow.closeEvent(self, event) def edit_settings(self): dialog = EditSettings(self, self.config_store) dialog.exec_() @QtCore.pyqtSlot() def about(self): dialog = QtGui.QMessageBox() dialog.setWindowTitle('Photini: about') dialog.setText( open(os.path.join(data_dir, 'about.html')).read() % (version)) dialog.setDetailedText( open(os.path.join(data_dir, 'LICENSE.txt')).read()) dialog.exec_() @QtCore.pyqtSlot(int, int) def new_split(self, pos, index): self.config_store.set( 'main_window', 'split', str(self.central_widget.sizes())) @QtCore.pyqtSlot(int) def new_tab(self, index): current = self.tabs.currentWidget() if current: current.refresh() self.image_list.emit_selection() @QtCore.pyqtSlot(list) def new_selection(self, selection): self.close_action.setEnabled(len(selection) > 0) self.tabs.currentWidget().new_selection(selection) @QtCore.pyqtSlot(bool) def new_metadata(self, unsaved_data): self.save_action.setEnabled(unsaved_data) def resizeEvent(self, event): size = self.width(), self.height() self.config_store.set('main_window', 'size', str(size))
def __init__(self, verbose): QtGui.QMainWindow.__init__(self) self.setWindowTitle("Photini photo metadata editor") self.selection = list() # logger window self.loggerwindow = LoggerWindow(verbose) self.logger = logging.getLogger(self.__class__.__name__) # config store self.config_store = ConfigStore() # set network proxy proxies = urllib2.getproxies() if 'http' in proxies: scheme, host, port = proxies['http'].split(':') QNetworkProxy.setApplicationProxy( QNetworkProxy(QNetworkProxy.HttpProxy, host[2:], int(port))) # restore size size = self.width(), self.height() self.resize(*eval( self.config_store.get('main_window', 'size', str(size)))) # image selector self.image_list = ImageList(self.config_store) 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' : '&Descriptive metadata', 'class' : Descriptive}, {'name' : '&Technical metadata', 'class' : Technical}, {'name' : 'Map (&Google)', 'class' : GoogleMap}, {'name' : 'Map (&Bing)', 'class' : BingMap}, {'name' : 'Map (&OSM)', 'class' : OpenStreetMap}, {'name' : '&Flickr uploader', 'class' : FlickrUploader}, {'name' : '&Picasa uploader', 'class' : PicasaUploader}, ) for tab in self.tab_list: tab['key'] = tab['name'].replace('&', '').replace(' ', '_') tab['key'] = tab['key'].replace('(', '').replace(')', '').lower() if tab['class']: tab['object'] = tab['class'](self.config_store, self.image_list) else: tab['object'] = None # file menu file_menu = self.menuBar().addMenu('File') open_action = QtGui.QAction('Open images', self) open_action.setShortcuts(['Ctrl+O']) open_action.triggered.connect(self.image_list.open_files) file_menu.addAction(open_action) self.save_action = QtGui.QAction('Save images with new data', self) self.save_action.setShortcuts(['Ctrl+S']) self.save_action.setEnabled(False) self.save_action.triggered.connect(self.image_list.save_files) file_menu.addAction(self.save_action) self.close_action = QtGui.QAction('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 = QtGui.QAction('Close all images', self) close_all_action.triggered.connect(self.close_all_files) file_menu.addAction(close_all_action) file_menu.addSeparator() quit_action = QtGui.QAction('Quit', self) quit_action.setShortcuts(['Ctrl+Q', 'Ctrl+W']) quit_action.triggered.connect(QtGui.qApp.closeAllWindows) file_menu.addAction(quit_action) # options menu options_menu = self.menuBar().addMenu('Options') settings_action = QtGui.QAction('Settings', self) settings_action.triggered.connect(self.edit_settings) options_menu.addAction(settings_action) options_menu.addSeparator() for tab in self.tab_list: tab['action'] = QtGui.QAction(tab['name'].replace('&', ''), self) tab['action'].setCheckable(True) if tab['class']: tab['action'].setChecked( eval(self.config_store.get('tabs', tab['key'], 'True'))) else: tab['action'].setEnabled(False) tab['action'].triggered.connect(self.add_tabs) options_menu.addAction(tab['action']) # help menu help_menu = self.menuBar().addMenu('Help') about_action = QtGui.QAction('About Photini', self) about_action.triggered.connect(self.about) help_menu.addAction(about_action) help_menu.addSeparator() help_action = QtGui.QAction('Photini documentation', self) help_action.triggered.connect(self.open_docs) help_menu.addAction(help_action) # main application area self.central_widget = QtGui.QSplitter() self.central_widget.setOrientation(Qt.Vertical) self.central_widget.setChildrenCollapsible(False) self.tabs = QtGui.QTabWidget() self.add_tabs() self.tabs.currentChanged.connect(self.new_tab) self.central_widget.addWidget(self.tabs) self.central_widget.addWidget(self.image_list) size = self.central_widget.sizes() self.central_widget.setSizes(eval( self.config_store.get('main_window', 'split', str(size)))) self.central_widget.splitterMoved.connect(self.new_split) self.setCentralWidget(self.central_widget)
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)
class MainWindow(QtWidgets.QMainWindow): 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) @QtSlot() @catch_all def open_initial_files(self): self.image_list.open_file_list(self.initial_files) @QtSlot() @catch_all def add_tabs(self): was_blocked = self.tabs.blockSignals(True) current = self.tabs.currentWidget() self.tabs.clear() idx = 0 for tab in self.tab_list: if not tab['class']: self.app.config_store.set('tabs', tab['module'], True) continue use_tab = tab['action'].isChecked() self.app.config_store.set('tabs', tab['module'], use_tab) if not use_tab: continue if 'object' not in tab: tab['object'] = tab['class'](self.image_list) self.tabs.addTab(tab['object'], tab['name']) self.tabs.setTabToolTip(idx, tab['name'].replace('&', '')) idx += 1 self.tabs.blockSignals(was_blocked) if current: self.tabs.setCurrentWidget(current) self.new_tab(-1) @QtSlot() @catch_all def open_docs(self): QtGui.QDesktopServices.openUrl( QtCore.QUrl('http://photini.readthedocs.io/')) @catch_all def closeEvent(self, event): for n in range(self.tabs.count()): if self.tabs.widget(n).do_not_close(): event.ignore() return self.image_list.unsaved_files_dialog(all_files=True, with_cancel=False) super(MainWindow, self).closeEvent(event) @QtSlot() @catch_all def edit_settings(self): dialog = EditSettings(self) if qt_version_info >= (6, 0): dialog.exec() else: dialog.exec_() self.tabs.currentWidget().refresh() @QtSlot(QtGui2.QAction) @catch_all def set_language(self, action): self.app.spell_check.set_language(action.data()) @QtSlot() @catch_all def about(self): text = """ <table width="100%"><tr> <td align="center" width="70%"> <h1>Photini</h1> <h3>version {0}</h3> <h4>build {1}</h4> </td> <td align="center"><img src="{2}" /></td> </tr></table> <p>© Jim Easterbrook <a href="mailto:[email protected]"> [email protected]</a><br /><br /> {3}<br /> {4}</p> """.format( __version__, build, pkg_resources.resource_filename('photini', 'data/icons/photini_128.png'), translate( 'MenuBar', 'An easy to use digital photograph metadata' ' (Exif, IPTC, XMP) editing application.'), translate( 'MenuBar', 'Open source package available from {}.').format( '<a href="https://github.com/jim-easterbrook/Photini">' 'github.com/jim-easterbrook/Photini</a>'), ) dialog = QtWidgets.QMessageBox(self) dialog.setWindowTitle(translate('MenuBar', 'Photini: about')) dialog.setText(text) licence = pkg_resources.resource_string('photini', 'data/LICENSE.txt') dialog.setDetailedText(licence.decode('utf-8')) dialog.setInformativeText( translate( 'MenuBar', 'This program is released with a GNU General Public' ' License. For details click the "{}" button.').format( dialog.buttons()[0].text())) if qt_version_info >= (6, 0): dialog.exec() else: dialog.exec_() @QtSlot() @catch_all def check_update(self): import requests with Busy(): try: rsp = requests.get('https://pypi.org/pypi/photini/json', timeout=20) except: logger.error(str(ex)) return if rsp.status_code != 200: logger.error('HTTP error %d', rsp.status_code) return release = rsp.json()['info']['version'] dialog = QtWidgets.QMessageBox(self) dialog.setWindowTitle(translate('MenuBar', 'Photini: version check')) dialog.setText( translate( 'MenuBar', 'You are currently running Photini version {0}. The' ' latest release is {1}.').format(__version__, release)) if qt_version_info >= (6, 0): dialog.exec() else: dialog.exec_() @QtSlot(int, int) @catch_all def new_split(self, pos, index): self.app.config_store.set('main_window', 'split', self.central_widget.sizes()) @QtSlot(int) @catch_all def new_tab(self, index): current = self.tabs.currentWidget() if current: self.image_list.set_drag_to_map(None) current.refresh() @QtSlot(list) @catch_all def new_selection(self, selection): self.image_list.configure_selected_actions(self.selected_actions) self.tabs.currentWidget().new_selection(selection) @QtSlot() @catch_all def new_image_list(self): for image in self.image_list.images: thumb = image.metadata.thumbnail if not thumb or not thumb['image']: self.fix_thumbs_action.setEnabled(True) return self.fix_thumbs_action.setEnabled(False) @QtSlot(bool) @catch_all def new_metadata(self, unsaved_data): self.image_list.configure_selected_actions(self.selected_actions) self.save_action.setEnabled(unsaved_data) @catch_all def resizeEvent(self, event): window_state = int(self.windowState()) self.app.config_store.set('main_window', 'state', window_state) if window_state & int(Qt.WindowMinimized | Qt.WindowMaximized | Qt.WindowFullScreen): return size = self.width(), self.height() self.app.config_store.set('main_window', 'size', size)
class MainWindow(QtWidgets.QMainWindow): 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/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.open_cage = OpenCage(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', '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.technical', 'photini.googlemap', 'photini.bingmap', 'photini.mapboxmap', 'photini.openstreetmap', 'photini.address', 'photini.flickr', 'photini.googlephotos', 'photini.importer' ] modules = eval( self.app.config_store.get('tabs', 'modules', pprint.pformat(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', 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(translate('MenuBar', 'File')) open_action = QtWidgets.QAction(translate('MenuBar', '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( translate('MenuBar', '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( translate('MenuBar', '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( translate('MenuBar', '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( translate('MenuBar', '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(translate('MenuBar', '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(translate('MenuBar', 'Options')) settings_action = QtWidgets.QAction(translate('MenuBar', '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(translate( 'MenuBar', 'Spelling')) enable_action = QtWidgets.QAction( translate('MenuBar', '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( translate('MenuBar', '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( translate('MenuBar', '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(translate('MenuBar', 'Help')) about_action = QtWidgets.QAction(translate('MenuBar', 'About Photini'), self) about_action.triggered.connect(self.about) help_menu.addAction(about_action) help_menu.addSeparator() help_action = QtWidgets.QAction( translate('MenuBar', '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) @QtCore.pyqtSlot() @catch_all def open_initial_files(self): self.image_list.open_file_list(self.initial_files) @QtCore.pyqtSlot(bool) @catch_all def add_tabs(self, checked): was_blocked = self.tabs.blockSignals(True) current = self.tabs.currentWidget() self.tabs.clear() idx = 0 for tab in self.tab_list: if not tab['class']: self.app.config_store.set('tabs', tab['module'], 'True') continue use_tab = tab['action'].isChecked() self.app.config_store.set('tabs', tab['module'], str(use_tab)) if not use_tab: continue if 'object' not in tab: tab['object'] = tab['class'](self.image_list) self.tabs.addTab(tab['object'], tab['name']) self.tabs.setTabToolTip(idx, tab['name'].replace('&', '')) idx += 1 self.tabs.blockSignals(was_blocked) if current: self.tabs.setCurrentWidget(current) self.new_tab(-1) @QtCore.pyqtSlot() @catch_all def open_docs(self): QtGui.QDesktopServices.openUrl( QtCore.QUrl('http://photini.readthedocs.io/')) @QtCore.pyqtSlot() @catch_all def close_files(self): self.image_list.close_files(False) @QtCore.pyqtSlot() @catch_all def close_all_files(self): self.image_list.close_files(True) @QtCore.pyqtSlot() @catch_all def import_pgx_file(self): importer = GpxImporter() importer.do_import(self) @catch_all def closeEvent(self, event): for n in range(self.tabs.count()): if self.tabs.widget(n).do_not_close(): event.ignore() return self.image_list.unsaved_files_dialog(all_files=True, with_cancel=False) super(MainWindow, self).closeEvent(event) @QtCore.pyqtSlot() @catch_all def edit_settings(self): dialog = EditSettings(self) dialog.exec_() self.tabs.currentWidget().refresh() @QtCore.pyqtSlot(QtWidgets.QAction) @catch_all def set_language(self, action): self.app.spell_check.set_language(action.data()) @QtCore.pyqtSlot() @catch_all def about(self): text = translate( 'MenuBar', """ <table width="100%"><tr> <td align="center" width="70%"> <h1>Photini</h1> <h3>version {0}</h3> <h4>build {1}</h4> </td> <td align="center"><img src="{2}" /></td> </tr></table> <p>© Jim Easterbrook <a href="mailto:[email protected]"> [email protected]</a><br /><br /> An easy to use digital photograph metadata editor.<br /> Open source package available from <a href="https://github.com/jim-easterbrook/Photini"> github.com/jim-easterbrook/Photini</a>.</p> <p>This program is released with a GNU General Public License. For details click the 'show details' button.</p> """).format( __version__, build, pkg_resources.resource_filename('photini', 'data/icons/128/photini.png')) dialog = QtWidgets.QMessageBox(self) dialog.setWindowTitle(translate('MenuBar', 'Photini: about')) dialog.setText(text) licence = pkg_resources.resource_string('photini', 'data/LICENSE.txt') dialog.setDetailedText(licence.decode('utf-8')) dialog.exec_() @QtCore.pyqtSlot(int, int) @catch_all def new_split(self, pos, index): self.app.config_store.set('main_window', 'split', str(self.central_widget.sizes())) @QtCore.pyqtSlot(int) @catch_all def new_tab(self, index): current = self.tabs.currentWidget() if current: self.image_list.set_drag_to_map(None) current.refresh() self.image_list.emit_selection() @QtCore.pyqtSlot(list) @catch_all def new_selection(self, selection): self.close_action.setEnabled(len(selection) > 0) if self.import_gpx_action: self.import_gpx_action.setEnabled(len(selection) > 0) self.tabs.currentWidget().new_selection(selection) @QtCore.pyqtSlot(bool) @catch_all def new_metadata(self, unsaved_data): self.save_action.setEnabled(unsaved_data) @catch_all def resizeEvent(self, event): size = self.width(), self.height() self.app.config_store.set('main_window', 'size', str(size))
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 # 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}, ) for tab in self.tab_list: if tab['class']: tab['object'] = tab['class'](self.image_list) else: tab['object'] = None # 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)
class MainWindow(QtWidgets.QMainWindow): 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 # 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}, ) for tab in self.tab_list: if tab['class']: tab['object'] = tab['class'](self.image_list) else: tab['object'] = None # 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) @QtCore.pyqtSlot() def open_initial_files(self): self.image_list.open_file_list(self.initial_files) @QtCore.pyqtSlot() def add_tabs(self): was_blocked = self.tabs.blockSignals(True) current = self.tabs.currentWidget() self.tabs.clear() for tab in self.tab_list: use_tab = tab['action'].isChecked() self.app.config_store.set('tabs', tab['key'], str(use_tab)) if tab['object'] and use_tab: self.tabs.addTab(tab['object'], tab['name']) self.tabs.blockSignals(was_blocked) if current: self.tabs.setCurrentWidget(current) self.new_tab(-1) @QtCore.pyqtSlot() def open_docs(self): webbrowser.open_new('http://photini.readthedocs.io/') @QtCore.pyqtSlot() def close_files(self): self._close_files(False) @QtCore.pyqtSlot() def close_all_files(self): self._close_files(True) def _close_files(self, all_files): if self.image_list.unsaved_files_dialog(all_files=all_files): self.image_list.close_files(all_files) def closeEvent(self, event): for n in range(self.tabs.count()): if self.tabs.widget(n).do_not_close(): event.ignore() return self.image_list.unsaved_files_dialog(all_files=True, with_cancel=False) super(MainWindow, self).closeEvent(event) @QtCore.pyqtSlot() def edit_settings(self): dialog = EditSettings(self) dialog.exec_() self.tabs.currentWidget().refresh() @QtCore.pyqtSlot() def about(self): text = self.tr(""" <table width="100%"><tr> <td align="center" width="70%"> <h1>Photini</h1> <h3>version {0}</h3> <h4>build {1}</h4> </td> <td align="center"><img src="{2}" /></td> </tr></table> <p>© Jim Easterbrook <a href="mailto:[email protected]"> [email protected]</a><br /><br /> An easy to use digital photograph metadata editor.<br /> Open source package available from <a href="https://github.com/jim-easterbrook/Photini"> github.com/jim-easterbrook/Photini</a>.</p> <p>This program is released with a GNU General Public License. For details click the 'show details' button.</p> """).format(__version__, build, pkg_resources.resource_filename( 'photini', 'data/icons/128/photini.png')) dialog = QtWidgets.QMessageBox(self) dialog.setWindowTitle(self.tr('Photini: about')) dialog.setText(text) licence = pkg_resources.resource_string('photini', 'data/LICENSE.txt') dialog.setDetailedText(licence.decode('utf-8')) dialog.exec_() @QtCore.pyqtSlot(int, int) def new_split(self, pos, index): self.app.config_store.set( 'main_window', 'split', str(self.central_widget.sizes())) @QtCore.pyqtSlot(int) def new_tab(self, index): current = self.tabs.currentWidget() if current: self.image_list.set_drag_to_map(None) current.refresh() self.image_list.emit_selection() @QtCore.pyqtSlot(list) def new_selection(self, selection): self.close_action.setEnabled(len(selection) > 0) self.tabs.currentWidget().new_selection(selection) @QtCore.pyqtSlot(bool) def new_metadata(self, unsaved_data): self.save_action.setEnabled(unsaved_data) def resizeEvent(self, event): size = self.width(), self.height() self.app.config_store.set('main_window', 'size', str(size))