Exemple #1
0
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>&copy; 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))
Exemple #2
0
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>&copy; 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))
Exemple #3
0
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>&copy; 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)
Exemple #4
0
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>&copy; 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))