예제 #1
0
파일: ui.py 프로젝트: bubok/calibre
 def a(name,
       text,
       icon,
       tb=None,
       sc_name=None,
       menu_name=None,
       popup_mode=QToolButton.MenuButtonPopup):
     name = 'action_' + name
     if isinstance(text, QDockWidget):
         ac = text.toggleViewAction()
         ac.setIcon(QIcon(I(icon)))
     else:
         ac = QAction(QIcon(I(icon)), text, self)
     setattr(self, name, ac)
     ac.setObjectName(name)
     (tb or self.tool_bar).addAction(ac)
     if sc_name:
         ac.setToolTip(
             unicode(ac.text()) +
             (' [%s]' %
              _(' or ').join(self.view.shortcuts.get_shortcuts(sc_name))
              ))
     if menu_name is not None:
         menu_name += '_menu'
         m = QMenu()
         setattr(self, menu_name, m)
         ac.setMenu(m)
         w = (tb or self.tool_bar).widgetForAction(ac)
         w.setPopupMode(popup_mode)
     return ac
예제 #2
0
 def _createAction(self, actionid, icon, text,
                   tooltip, slot, toggled=False):
     """
     Creates the new internal action with given identifier.
     """
     action = QAction(icon, text, self)
     action.setToolTip(text)
     action.setStatusTip(tooltip)
     action.setCheckable(toggled)
     if slot is not None:
         if action.isCheckable():
             action.toggled.connect(slot)
         else:
             action.triggered.connect(slot)
     self._actions[actionid] = action
     return action
예제 #3
0
파일: ui.py 프로젝트: Cykooz/calibre
 def a(name, text, icon, tb=None, sc_name=None, menu_name=None, popup_mode=QToolButton.MenuButtonPopup):
     name = 'action_' + name
     if isinstance(text, QDockWidget):
         ac = text.toggleViewAction()
         ac.setIcon(QIcon(I(icon)))
     else:
         ac = QAction(QIcon(I(icon)), text, self)
     setattr(self, name, ac)
     ac.setObjectName(name)
     (tb or self.tool_bar).addAction(ac)
     if sc_name:
         ac.setToolTip(unicode(ac.text()) + (' [%s]' % _(' or ').join(self.view.shortcuts.get_shortcuts(sc_name))))
     if menu_name is not None:
         menu_name += '_menu'
         m = QMenu()
         setattr(self, menu_name, m)
         ac.setMenu(m)
         w = (tb or self.tool_bar).widgetForAction(ac)
         w.setPopupMode(popup_mode)
     return ac
예제 #4
0
class PluginUpdaterDialog(SizePersistedDialog):

    initial_extra_size = QSize(350, 100)
    forum_label_text = _('Plugin homepage')

    def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE):
        SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog')
        self.gui = gui
        self.forum_link = None
        self.zip_url = None
        self.model = None
        self.do_restart = False
        self._initialize_controls()
        self._create_context_menu()

        try:
            display_plugins = read_available_plugins(raise_error=True)
        except Exception:
            display_plugins = []
            import traceback
            error_dialog(self.gui, _('Update Check Failed'),
                        _('Unable to reach the plugin index page.'),
                        det_msg=traceback.format_exc(), show=True)

        if display_plugins:
            self.model = DisplayPluginModel(display_plugins)
            self.proxy_model = DisplayPluginSortFilterModel(self)
            self.proxy_model.setSourceModel(self.model)
            self.plugin_view.setModel(self.proxy_model)
            self.plugin_view.resizeColumnsToContents()
            self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed)
            self.plugin_view.doubleClicked.connect(self.install_button.click)
            self.filter_combo.setCurrentIndex(initial_filter)
            self._select_and_focus_view()
        else:
            self.filter_combo.setEnabled(False)
        # Cause our dialog size to be restored from prefs or created on first usage
        self.resize_dialog()

    def _initialize_controls(self):
        self.setWindowTitle(_('User plugins'))
        self.setWindowIcon(QIcon(I('plugins/plugin_updater.png')))
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png',
                _('User plugins'))
        layout.addLayout(title_layout)

        header_layout = QHBoxLayout()
        layout.addLayout(header_layout)
        self.filter_combo = PluginFilterComboBox(self)
        self.filter_combo.setMinimumContentsLength(20)
        self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed)
        la = QLabel(_('Filter list of &plugins')+':', self)
        la.setBuddy(self.filter_combo)
        header_layout.addWidget(la)
        header_layout.addWidget(self.filter_combo)
        header_layout.addStretch(10)

        # filter plugins by name
        la = QLabel(_('Filter by &name')+':', self)
        header_layout.addWidget(la)
        self.filter_by_name_lineedit = QLineEdit(self)
        la.setBuddy(self.filter_by_name_lineedit)
        self.filter_by_name_lineedit.setText("")
        self.filter_by_name_lineedit.textChanged.connect(self._filter_name_lineedit_changed)

        header_layout.addWidget(self.filter_by_name_lineedit)

        self.plugin_view = QTableView(self)
        self.plugin_view.horizontalHeader().setStretchLastSection(True)
        self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.plugin_view.setAlternatingRowColors(True)
        self.plugin_view.setSortingEnabled(True)
        self.plugin_view.setIconSize(QSize(28, 28))
        layout.addWidget(self.plugin_view)

        details_layout = QHBoxLayout()
        layout.addLayout(details_layout)
        forum_label = self.forum_label = QLabel('')
        forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
        forum_label.linkActivated.connect(self._forum_label_activated)
        details_layout.addWidget(QLabel(_('Description')+':', self), 0, Qt.AlignLeft)
        details_layout.addWidget(forum_label, 1, Qt.AlignRight)

        self.description = QLabel(self)
        self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.description.setMinimumHeight(40)
        self.description.setWordWrap(True)
        layout.addWidget(self.description)

        self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
        self.button_box.rejected.connect(self.reject)
        self.finished.connect(self._finished)
        self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.AcceptRole)
        self.install_button.setToolTip(_('Install the selected plugin'))
        self.install_button.clicked.connect(self._install_clicked)
        self.install_button.setEnabled(False)
        self.configure_button = self.button_box.addButton(' '+_('&Customize plugin ')+' ', QDialogButtonBox.ResetRole)
        self.configure_button.setToolTip(_('Customize the options for this plugin'))
        self.configure_button.clicked.connect(self._configure_clicked)
        self.configure_button.setEnabled(False)
        layout.addWidget(self.button_box)

    def update_forum_label(self):
        txt = ''
        if self.forum_link:
            txt = '<a href="%s">%s</a>' % (self.forum_link, self.forum_label_text)
        self.forum_label.setText(txt)

    def _create_context_menu(self):
        self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.install_action = QAction(QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self)
        self.install_action.setToolTip(_('Install the selected plugin'))
        self.install_action.triggered.connect(self._install_clicked)
        self.install_action.setEnabled(False)
        self.plugin_view.addAction(self.install_action)
        self.history_action = QAction(QIcon(I('chapters.png')), _('Version &history'), self)
        self.history_action.setToolTip(_('Show history of changes to this plugin'))
        self.history_action.triggered.connect(self._history_clicked)
        self.history_action.setEnabled(False)
        self.plugin_view.addAction(self.history_action)
        self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &forum thread'), self)
        self.forum_action.triggered.connect(self._forum_label_activated)
        self.forum_action.setEnabled(False)
        self.plugin_view.addAction(self.forum_action)

        sep1 = QAction(self)
        sep1.setSeparator(True)
        self.plugin_view.addAction(sep1)

        self.toggle_enabled_action = QAction(_('Enable/&disable plugin'), self)
        self.toggle_enabled_action.setToolTip(_('Enable or disable this plugin'))
        self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked)
        self.toggle_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.toggle_enabled_action)
        self.uninstall_action = QAction(_('&Remove plugin'), self)
        self.uninstall_action.setToolTip(_('Uninstall the selected plugin'))
        self.uninstall_action.triggered.connect(self._uninstall_clicked)
        self.uninstall_action.setEnabled(False)
        self.plugin_view.addAction(self.uninstall_action)

        sep2 = QAction(self)
        sep2.setSeparator(True)
        self.plugin_view.addAction(sep2)

        self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self)
        self.donate_enabled_action.setToolTip(_('Donate to the developer of this plugin'))
        self.donate_enabled_action.triggered.connect(self._donate_clicked)
        self.donate_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.donate_enabled_action)

        sep3 = QAction(self)
        sep3.setSeparator(True)
        self.plugin_view.addAction(sep3)

        self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self)
        self.configure_action.setToolTip(_('Customize the options for this plugin'))
        self.configure_action.triggered.connect(self._configure_clicked)
        self.configure_action.setEnabled(False)
        self.plugin_view.addAction(self.configure_action)

    def _finished(self, *args):
        if self.model:
            update_plugins = list(filter(filter_upgradeable_plugins, self.model.display_plugins))
            self.gui.recalc_update_label(len(update_plugins))

    def _plugin_current_changed(self, current, previous):
        if current.isValid():
            actual_idx = self.proxy_model.mapToSource(current)
            display_plugin = self.model.display_plugins[actual_idx.row()]
            self.description.setText(display_plugin.description)
            self.forum_link = display_plugin.forum_link
            self.zip_url = display_plugin.zip_url
            self.forum_action.setEnabled(bool(self.forum_link))
            self.install_button.setEnabled(display_plugin.is_valid_to_install())
            self.install_action.setEnabled(self.install_button.isEnabled())
            self.uninstall_action.setEnabled(display_plugin.is_installed())
            self.history_action.setEnabled(display_plugin.has_changelog)
            self.configure_button.setEnabled(display_plugin.is_installed())
            self.configure_action.setEnabled(self.configure_button.isEnabled())
            self.toggle_enabled_action.setEnabled(display_plugin.is_installed())
            self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link))
        else:
            self.description.setText('')
            self.forum_link = None
            self.zip_url = None
            self.forum_action.setEnabled(False)
            self.install_button.setEnabled(False)
            self.install_action.setEnabled(False)
            self.uninstall_action.setEnabled(False)
            self.history_action.setEnabled(False)
            self.configure_button.setEnabled(False)
            self.configure_action.setEnabled(False)
            self.toggle_enabled_action.setEnabled(False)
            self.donate_enabled_action.setEnabled(False)
        self.update_forum_label()

    def _donate_clicked(self):
        plugin = self._selected_display_plugin()
        if plugin and plugin.donation_link:
            open_url(QUrl(plugin.donation_link))

    def _select_and_focus_view(self, change_selection=True):
        if change_selection and self.plugin_view.model().rowCount() > 0:
            self.plugin_view.selectRow(0)
        else:
            idx = self.plugin_view.selectionModel().currentIndex()
            self._plugin_current_changed(idx, 0)
        self.plugin_view.setFocus()

    def _filter_combo_changed(self, idx):
        self.filter_by_name_lineedit.setText("")  # clear the name filter text when a different group was selected
        self.proxy_model.set_filter_criteria(idx)
        if idx == FILTER_NOT_INSTALLED:
            self.plugin_view.sortByColumn(5, Qt.DescendingOrder)
        else:
            self.plugin_view.sortByColumn(0, Qt.AscendingOrder)
        self._select_and_focus_view()

    def _filter_name_lineedit_changed(self, text):
        self.proxy_model.set_filter_text(text)  # set the filter text for filterAcceptsRow

    def _forum_label_activated(self):
        if self.forum_link:
            open_url(QUrl(self.forum_link))

    def _selected_display_plugin(self):
        idx = self.plugin_view.selectionModel().currentIndex()
        actual_idx = self.proxy_model.mapToSource(idx)
        return self.model.display_plugins[actual_idx.row()]

    def _uninstall_plugin(self, name_to_remove):
        if DEBUG:
            prints('Removing plugin: ', name_to_remove)
        remove_plugin(name_to_remove)
        # Make sure that any other plugins that required this plugin
        # to be uninstalled first have the requirement removed
        for display_plugin in self.model.display_plugins:
            # Make sure we update the status and display of the
            # plugin we just uninstalled
            if name_to_remove in display_plugin.uninstall_plugins:
                if DEBUG:
                    prints('Removing uninstall dependency for: ', display_plugin.name)
                display_plugin.uninstall_plugins.remove(name_to_remove)
            if display_plugin.qname == name_to_remove:
                if DEBUG:
                    prints('Resetting plugin to uninstalled status: ', display_plugin.name)
                display_plugin.installed_version = None
                display_plugin.plugin = None
                display_plugin.uninstall_plugins = []
                if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
                    self.model.refresh_plugin(display_plugin)

    def _uninstall_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(self, _('Are you sure?'), '<p>'+
                   _('Are you sure you want to uninstall the <b>%s</b> plugin?')%display_plugin.name,
                   show_copy_button=False):
            return
        self._uninstall_plugin(display_plugin.qname)
        if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
            self.model.beginResetModel(), self.model.endResetModel()
            self._select_and_focus_view()
        else:
            self._select_and_focus_view(change_selection=False)

    def _install_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(self, _('Install %s')%display_plugin.name, '<p>' +
                _('Installing plugins is a <b>security risk</b>. '
                'Plugins can contain a virus/malware. '
                    'Only install it if you got it from a trusted source.'
                    ' Are you sure you want to proceed?'),
                show_copy_button=False):
            return

        if display_plugin.uninstall_plugins:
            uninstall_names = list(display_plugin.uninstall_plugins)
            if DEBUG:
                prints('Uninstalling plugin: ', ', '.join(uninstall_names))
            for name_to_remove in uninstall_names:
                self._uninstall_plugin(name_to_remove)

        plugin_zip_url = display_plugin.zip_url
        if DEBUG:
            prints('Downloading plugin ZIP attachment: ', plugin_zip_url)
        self.gui.status_bar.showMessage(_('Downloading plugin ZIP attachment: %s') % plugin_zip_url)
        zip_path = self._download_zip(plugin_zip_url)

        if DEBUG:
            prints('Installing plugin: ', zip_path)
        self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path)

        do_restart = False
        try:
            from calibre.customize.ui import config
            installed_plugins = frozenset(config['plugins'])
            try:
                plugin = add_plugin(zip_path)
            except NameConflict as e:
                return error_dialog(self.gui, _('Already exists'),
                        unicode_type(e), show=True)
            # Check for any toolbars to add to.
            widget = ConfigWidget(self.gui)
            widget.gui = self.gui
            widget.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins)
            self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name)
            d = info_dialog(self.gui, _('Success'),
                    _('Plugin <b>{0}</b> successfully installed under <b>'
                        ' {1} plugins</b>. You may have to restart calibre '
                        'for the plugin to take effect.').format(plugin.name, plugin.type),
                    show_copy_button=False)
            b = d.bb.addButton(_('&Restart calibre now'), d.bb.AcceptRole)
            b.setIcon(QIcon(I('lt.png')))
            d.do_restart = False

            def rf():
                d.do_restart = True
            b.clicked.connect(rf)
            d.set_details('')
            d.exec_()
            b.clicked.disconnect()
            do_restart = d.do_restart

            display_plugin.plugin = plugin
            # We cannot read the 'actual' version information as the plugin will not be loaded yet
            display_plugin.installed_version = display_plugin.available_version
        except:
            if DEBUG:
                prints('ERROR occurred while installing plugin: %s'%display_plugin.name)
                traceback.print_exc()
            error_dialog(self.gui, _('Install plugin failed'),
                         _('A problem occurred while installing this plugin.'
                           ' This plugin will now be uninstalled.'
                           ' Please post the error message in details below into'
                           ' the forum thread for this plugin and restart calibre.'),
                         det_msg=traceback.format_exc(), show=True)
            if DEBUG:
                prints('Due to error now uninstalling plugin: %s'%display_plugin.name)
            remove_plugin(display_plugin.name)
            display_plugin.plugin = None

        display_plugin.uninstall_plugins = []
        if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]:
            self.model.beginResetModel(), self.model.endResetModel()
            self._select_and_focus_view()
        else:
            self.model.refresh_plugin(display_plugin)
            self._select_and_focus_view(change_selection=False)
        if do_restart:
            self.do_restart = True
            self.accept()

    def _history_clicked(self):
        display_plugin = self._selected_display_plugin()
        text = self._read_version_history_html(display_plugin.forum_link)
        if text:
            dlg = VersionHistoryDialog(self, display_plugin.name, text)
            dlg.exec_()
        else:
            return error_dialog(self, _('Version history missing'),
                _('Unable to find the version history for %s')%display_plugin.name,
                show=True)

    def _configure_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.is_customizable():
            return info_dialog(self, _('Plugin not customizable'),
                _('Plugin: %s does not need customization')%plugin.name, show=True)
        from calibre.customize import InterfaceActionBase
        if isinstance(plugin, InterfaceActionBase) and not getattr(plugin,
                'actual_iaction_plugin_loaded', False):
            return error_dialog(self, _('Must restart'),
                    _('You must restart calibre before you can'
                        ' configure the <b>%s</b> plugin')%plugin.name, show=True)
        plugin.do_user_config(self.parent())

    def _toggle_enabled_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.can_be_disabled:
            return error_dialog(self,_('Plugin cannot be disabled'),
                         _('The plugin: %s cannot be disabled')%plugin.name, show=True)
        if is_disabled(plugin):
            enable_plugin(plugin)
        else:
            disable_plugin(plugin)
        self.model.refresh_plugin(display_plugin)

    def _read_version_history_html(self, forum_link):
        br = browser()
        br.set_handle_gzip(True)
        try:
            raw = br.open_novisit(forum_link).read()
            if not raw:
                return None
        except:
            traceback.print_exc()
            return None
        raw = raw.decode('utf-8', errors='replace')
        root = html.fromstring(raw)
        spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]')
        for spoiler_node in spoiler_nodes:
            try:
                if spoiler_node.getprevious() is None:
                    # This is a spoiler node that has been indented using [INDENT]
                    # Need to go up to parent div, then previous node to get header
                    heading_node = spoiler_node.getparent().getprevious()
                else:
                    # This is a spoiler node after a BR tag from the heading
                    heading_node = spoiler_node.getprevious().getprevious()
                if heading_node is None:
                    continue
                if heading_node.text_content().lower().find('version history') != -1:
                    div_node = spoiler_node.xpath('div')[0]
                    text = html.tostring(div_node, method='html', encoding='unicode')
                    return re.sub(r'<div\s.*?>', '<div>', text)
            except:
                if DEBUG:
                    prints('======= MobileRead Parse Error =======')
                    traceback.print_exc()
                    prints(html.tostring(spoiler_node))
        return None

    def _download_zip(self, plugin_zip_url):
        from calibre.ptempfile import PersistentTemporaryFile
        raw = get_https_resource_securely(plugin_zip_url, headers={'User-Agent':'%s %s' % (__appname__, __version__)})
        with PersistentTemporaryFile('.zip') as pt:
            pt.write(raw)
        return pt.name
예제 #5
0
class EditorWidget(QWebView, LineEditECM):  # {{{

    data_changed = pyqtSignal()

    def __init__(self, parent=None):
        QWebView.__init__(self, parent)
        self.base_url = None
        self._parent = weakref.ref(parent)
        self.readonly = False

        self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL)

        extra_shortcuts = {
            'ToggleBold': 'Bold',
            'ToggleItalic': 'Italic',
            'ToggleUnderline': 'Underline',
        }

        for wac, name, icon, text, checkable in [
            ('ToggleBold', 'bold', 'format-text-bold', _('Bold'), True),
            ('ToggleItalic', 'italic', 'format-text-italic', _('Italic'),
             True),
            ('ToggleUnderline', 'underline', 'format-text-underline',
             _('Underline'), True),
            ('ToggleStrikethrough', 'strikethrough',
             'format-text-strikethrough', _('Strikethrough'), True),
            ('ToggleSuperscript', 'superscript', 'format-text-superscript',
             _('Superscript'), True),
            ('ToggleSubscript', 'subscript', 'format-text-subscript',
             _('Subscript'), True),
            ('InsertOrderedList', 'ordered_list', 'format-list-ordered',
             _('Ordered list'), True),
            ('InsertUnorderedList', 'unordered_list', 'format-list-unordered',
             _('Unordered list'), True),
            ('AlignLeft', 'align_left', 'format-justify-left', _('Align left'),
             False),
            ('AlignCenter', 'align_center', 'format-justify-center',
             _('Align center'), False),
            ('AlignRight', 'align_right', 'format-justify-right',
             _('Align right'), False),
            ('AlignJustified', 'align_justified', 'format-justify-fill',
             _('Align justified'), False),
            ('Undo', 'undo', 'edit-undo', _('Undo'), False),
            ('Redo', 'redo', 'edit-redo', _('Redo'), False),
            ('RemoveFormat', 'remove_format', 'edit-clear',
             _('Remove formatting'), False),
            ('Copy', 'copy', 'edit-copy', _('Copy'), False),
            ('Paste', 'paste', 'edit-paste', _('Paste'), False),
            ('Cut', 'cut', 'edit-cut', _('Cut'), False),
            ('Indent', 'indent', 'format-indent-more',
             _('Increase indentation'), False),
            ('Outdent', 'outdent', 'format-indent-less',
             _('Decrease indentation'), False),
            ('SelectAll', 'select_all', 'edit-select-all', _('Select all'),
             False),
        ]:
            ac = PageAction(wac, icon, text, checkable, self)
            setattr(self, 'action_' + name, ac)
            ss = extra_shortcuts.get(wac, None)
            if ss:
                ac.setShortcut(QKeySequence(getattr(QKeySequence, ss)))
            if wac == 'RemoveFormat':
                ac.triggered.connect(self.remove_format_cleanup,
                                     type=Qt.QueuedConnection)

        self.action_color = QAction(QIcon(I('format-text-color.png')),
                                    _('Foreground color'), self)
        self.action_color.triggered.connect(self.foreground_color)

        self.action_background = QAction(QIcon(I('format-fill-color.png')),
                                         _('Background color'), self)
        self.action_background.triggered.connect(self.background_color)

        self.action_block_style = QAction(QIcon(I('format-text-heading.png')),
                                          _('Style text block'), self)
        self.action_block_style.setToolTip(_('Style the selected text block'))
        self.block_style_menu = QMenu(self)
        self.action_block_style.setMenu(self.block_style_menu)
        self.block_style_actions = []
        for text, name in [
            (_('Normal'), 'p'),
            (_('Heading') + ' 1', 'h1'),
            (_('Heading') + ' 2', 'h2'),
            (_('Heading') + ' 3', 'h3'),
            (_('Heading') + ' 4', 'h4'),
            (_('Heading') + ' 5', 'h5'),
            (_('Heading') + ' 6', 'h6'),
            (_('Pre-formatted'), 'pre'),
            (_('Blockquote'), 'blockquote'),
            (_('Address'), 'address'),
        ]:
            ac = BlockStyleAction(text, name, self)
            self.block_style_menu.addAction(ac)
            self.block_style_actions.append(ac)

        self.action_insert_link = QAction(QIcon(I('insert-link.png')),
                                          _('Insert link or image'), self)
        self.action_insert_hr = QAction(QIcon(I('format-text-hr.png')),
                                        _('Insert separator'), self)
        self.action_insert_link.triggered.connect(self.insert_link)
        self.action_insert_hr.triggered.connect(self.insert_hr)
        self.pageAction(QWebPage.ToggleBold).changed.connect(
            self.update_link_action)
        self.action_insert_link.setEnabled(False)
        self.action_insert_hr.setEnabled(False)
        self.action_clear = QAction(QIcon(I('trash.png')), _('Clear'), self)
        self.action_clear.triggered.connect(self.clear_text)

        self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.page().linkClicked.connect(self.link_clicked)
        secure_web_page(self.page().settings())

        self.setHtml('')
        self.set_readonly(False)
        self.page().contentsChanged.connect(self.data_changed)

    def update_link_action(self):
        wac = self.pageAction(QWebPage.ToggleBold).isEnabled()
        self.action_insert_link.setEnabled(wac)
        self.action_insert_hr.setEnabled(wac)

    def set_readonly(self, what):
        self.readonly = what
        self.page().setContentEditable(not self.readonly)

    def clear_text(self, *args):
        us = self.page().undoStack()
        us.beginMacro('clear all text')
        self.action_select_all.trigger()
        self.action_remove_format.trigger()
        self.exec_command('delete')
        us.endMacro()
        self.set_font_style()
        self.setFocus(Qt.OtherFocusReason)

    def link_clicked(self, url):
        open_url(url)

    def foreground_color(self):
        col = QColorDialog.getColor(Qt.black, self,
                                    _('Choose foreground color'),
                                    QColorDialog.ShowAlphaChannel)
        if col.isValid():
            self.exec_command('foreColor', unicode_type(col.name()))

    def background_color(self):
        col = QColorDialog.getColor(Qt.white, self,
                                    _('Choose background color'),
                                    QColorDialog.ShowAlphaChannel)
        if col.isValid():
            self.exec_command('hiliteColor', unicode_type(col.name()))

    def insert_hr(self, *args):
        self.exec_command('insertHTML', '<hr>')

    def insert_link(self, *args):
        link, name, is_image = self.ask_link()
        if not link:
            return
        url = self.parse_link(link)
        if url.isValid():
            url = unicode_type(url.toString(NO_URL_FORMATTING))
            self.setFocus(Qt.OtherFocusReason)
            if is_image:
                self.exec_command(
                    'insertHTML', '<img src="%s" alt="%s"></img>' %
                    (prepare_string_for_xml(url, True),
                     prepare_string_for_xml(name or _('Image'), True)))
            elif name:
                self.exec_command(
                    'insertHTML',
                    '<a href="%s">%s</a>' % (prepare_string_for_xml(
                        url, True), prepare_string_for_xml(name)))
            else:
                self.exec_command('createLink', url)
        else:
            error_dialog(self,
                         _('Invalid URL'),
                         _('The url %r is invalid') % link,
                         show=True)

    def ask_link(self):
        d = QDialog(self)
        d.setWindowTitle(_('Create link'))
        l = QFormLayout()
        l.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
        d.setLayout(l)
        d.url = QLineEdit(d)
        d.name = QLineEdit(d)
        d.treat_as_image = QCheckBox(d)
        d.setMinimumWidth(600)
        d.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        d.br = b = QPushButton(_('&Browse'))
        b.setIcon(QIcon(I('document_open.png')))

        def cf():
            files = choose_files(d,
                                 'select link file',
                                 _('Choose file'),
                                 select_only_single_file=True)
            if files:
                path = files[0]
                d.url.setText(path)
                if path and os.path.exists(path):
                    with lopen(path, 'rb') as f:
                        q = what(f)
                    is_image = q in {'jpeg', 'png', 'gif'}
                    d.treat_as_image.setChecked(is_image)

        b.clicked.connect(cf)
        d.la = la = QLabel(
            _('Enter a URL. If you check the "Treat the URL as an image" box '
              'then the URL will be added as an image reference instead of as '
              'a link. You can also choose to create a link to a file on '
              'your computer. '
              'Note that if you create a link to a file on your computer, it '
              'will stop working if the file is moved.'))
        la.setWordWrap(True)
        la.setStyleSheet('QLabel { margin-bottom: 1.5ex }')
        l.setWidget(0, l.SpanningRole, la)
        l.addRow(_('Enter &URL:'), d.url)
        l.addRow(_('Treat the URL as an &image'), d.treat_as_image)
        l.addRow(_('Enter &name (optional):'), d.name)
        l.addRow(_('Choose a file on your computer:'), d.br)
        l.addRow(d.bb)
        d.bb.accepted.connect(d.accept)
        d.bb.rejected.connect(d.reject)
        d.resize(d.sizeHint())
        link, name, is_image = None, None, False
        if d.exec_() == d.Accepted:
            link, name = unicode_type(d.url.text()).strip(), unicode_type(
                d.name.text()).strip()
            is_image = d.treat_as_image.isChecked()
        return link, name, is_image

    def parse_link(self, link):
        link = link.strip()
        if link and os.path.exists(link):
            return QUrl.fromLocalFile(link)
        has_schema = re.match(r'^[a-zA-Z]+:', link)
        if has_schema is not None:
            url = QUrl(link, QUrl.TolerantMode)
            if url.isValid():
                return url
        if os.path.exists(link):
            return QUrl.fromLocalFile(link)

        if has_schema is None:
            first, _, rest = link.partition('.')
            prefix = 'http'
            if first == 'ftp':
                prefix = 'ftp'
            url = QUrl(prefix + '://' + link, QUrl.TolerantMode)
            if url.isValid():
                return url

        return QUrl(link, QUrl.TolerantMode)

    def sizeHint(self):
        return QSize(150, 150)

    def exec_command(self, cmd, arg=None):
        frame = self.page().mainFrame()
        if arg is not None:
            js = 'document.execCommand("%s", false, %s);' % (
                cmd, json.dumps(unicode_type(arg)))
        else:
            js = 'document.execCommand("%s", false, null);' % cmd
        frame.evaluateJavaScript(js)

    def remove_format_cleanup(self):
        self.html = self.html

    @property
    def html(self):
        ans = u''
        try:
            if not self.page().mainFrame().documentElement().findFirst(
                    'meta[name="calibre-dont-sanitize"]').isNull():
                # Bypass cleanup if special meta tag exists
                return unicode_type(self.page().mainFrame().toHtml())
            check = unicode_type(self.page().mainFrame().toPlainText()).strip()
            raw = unicode_type(self.page().mainFrame().toHtml())
            raw = xml_to_unicode(raw,
                                 strip_encoding_pats=True,
                                 resolve_entities=True)[0]
            raw = self.comments_pat.sub('', raw)
            if not check and '<img' not in raw.lower():
                return ans

            try:
                root = html.fromstring(raw)
            except Exception:
                root = parse(raw, maybe_xhtml=False, sanitize_names=True)

            elems = []
            for body in root.xpath('//body'):
                if body.text:
                    elems.append(body.text)
                elems += [
                    html.tostring(x, encoding=unicode_type) for x in body
                    if x.tag not in ('script', 'style')
                ]

            if len(elems) > 1:
                ans = u'<div>%s</div>' % (u''.join(elems))
            else:
                ans = u''.join(elems)
                if not ans.startswith('<'):
                    ans = '<p>%s</p>' % ans
            ans = xml_replace_entities(ans)
        except:
            import traceback
            traceback.print_exc()

        return ans

    @html.setter
    def html(self, val):
        if self.base_url is None:
            self.setHtml(val)
        else:
            self.setHtml(val, self.base_url)
        self.set_font_style()

    def set_base_url(self, qurl):
        self.base_url = qurl
        self.setHtml('', self.base_url)

    def set_html(self, val, allow_undo=True):
        if not allow_undo or self.readonly:
            self.html = val
            return
        mf = self.page().mainFrame()
        mf.evaluateJavaScript('document.execCommand("selectAll", false, null)')
        mf.evaluateJavaScript('document.execCommand("insertHTML", false, %s)' %
                              json.dumps(unicode_type(val)))
        self.set_font_style()

    def set_font_style(self):
        fi = QFontInfo(QApplication.font(self))
        f = fi.pixelSize() + 1 + int(
            tweaks['change_book_details_font_size_by'])
        fam = unicode_type(fi.family()).strip().replace('"', '')
        if not fam:
            fam = 'sans-serif'
        style = 'font-size: %fpx; font-family:"%s",sans-serif;' % (f, fam)

        # toList() is needed because PyQt on Debian is old/broken
        for body in self.page().mainFrame().documentElement().findAll(
                'body').toList():
            body.setAttribute('style', style)
        self.page().setContentEditable(not self.readonly)

    def event(self, ev):
        if ev.type() in (ev.KeyPress,
                         ev.KeyRelease, ev.ShortcutOverride) and hasattr(
                             ev,
                             'key') and ev.key() in (Qt.Key_Tab, Qt.Key_Escape,
                                                     Qt.Key_Backtab):
            if (ev.key() == Qt.Key_Tab and ev.modifiers() & Qt.ControlModifier
                    and ev.type() == ev.KeyPress):
                self.exec_command('insertHTML',
                                  '<span style="white-space:pre">\t</span>')
                ev.accept()
                return True
            ev.ignore()
            return False
        return QWebView.event(self, ev)

    def text(self):
        return self.page().selectedText()

    def setText(self, text):
        self.exec_command('insertText', text)

    def contextMenuEvent(self, ev):
        menu = self.page().createStandardContextMenu()
        paste = self.pageAction(QWebPage.Paste)
        for action in menu.actions():
            if action == paste:
                menu.insertAction(action,
                                  self.pageAction(QWebPage.PasteAndMatchStyle))
        st = self.text()
        if st and st.strip():
            self.create_change_case_menu(menu)
        parent = self._parent()
        if hasattr(parent, 'toolbars_visible'):
            vis = parent.toolbars_visible
            menu.addAction(
                _('%s toolbars') % (_('Hide') if vis else _('Show')),
                parent.toggle_toolbars)
        menu.exec_(ev.globalPos())
예제 #6
0
class PluginUpdaterDialog(SizePersistedDialog):

    initial_extra_size = QSize(350, 100)
    forum_label_text = _('Plugin homepage')

    def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE):
        SizePersistedDialog.__init__(
            self, gui, 'Plugin Updater plugin:plugin updater dialog')
        self.gui = gui
        self.forum_link = None
        self.zip_url = None
        self.model = None
        self.do_restart = False
        self._initialize_controls()
        self._create_context_menu()

        display_plugins = read_available_plugins()

        if display_plugins:
            self.model = DisplayPluginModel(display_plugins)
            self.proxy_model = DisplayPluginSortFilterModel(self)
            self.proxy_model.setSourceModel(self.model)
            self.plugin_view.setModel(self.proxy_model)
            self.plugin_view.resizeColumnsToContents()
            self.plugin_view.selectionModel().currentRowChanged.connect(
                self._plugin_current_changed)
            self.plugin_view.doubleClicked.connect(self.install_button.click)
            self.filter_combo.setCurrentIndex(initial_filter)
            self._select_and_focus_view()
        else:
            error_dialog(self.gui,
                         _('Update Check Failed'),
                         _('Unable to reach the plugin index page.'),
                         det_msg=INDEX_URL,
                         show=True)
            self.filter_combo.setEnabled(False)
        # Cause our dialog size to be restored from prefs or created on first usage
        self.resize_dialog()

    def _initialize_controls(self):
        self.setWindowTitle(_('User plugins'))
        self.setWindowIcon(QIcon(I('plugins/plugin_updater.png')))
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png',
                                        _('User Plugins'))
        layout.addLayout(title_layout)

        header_layout = QHBoxLayout()
        layout.addLayout(header_layout)
        self.filter_combo = PluginFilterComboBox(self)
        self.filter_combo.setMinimumContentsLength(20)
        self.filter_combo.currentIndexChanged[int].connect(
            self._filter_combo_changed)
        header_layout.addWidget(QLabel(
            _('Filter list of plugins') + ':', self))
        header_layout.addWidget(self.filter_combo)
        header_layout.addStretch(10)

        # filter plugins by name
        header_layout.addWidget(QLabel(_('Filter by name') + ':', self))
        self.filter_by_name_lineedit = QLineEdit(self)
        self.filter_by_name_lineedit.setText("")
        self.filter_by_name_lineedit.textChanged.connect(
            self._filter_name_lineedit_changed)

        header_layout.addWidget(self.filter_by_name_lineedit)

        self.plugin_view = QTableView(self)
        self.plugin_view.horizontalHeader().setStretchLastSection(True)
        self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.plugin_view.setAlternatingRowColors(True)
        self.plugin_view.setSortingEnabled(True)
        self.plugin_view.setIconSize(QSize(28, 28))
        layout.addWidget(self.plugin_view)

        details_layout = QHBoxLayout()
        layout.addLayout(details_layout)
        forum_label = self.forum_label = QLabel('')
        forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse
                                            | Qt.LinksAccessibleByKeyboard)
        forum_label.linkActivated.connect(self._forum_label_activated)
        details_layout.addWidget(QLabel(_('Description') + ':', self), 0,
                                 Qt.AlignLeft)
        details_layout.addWidget(forum_label, 1, Qt.AlignRight)

        self.description = QLabel(self)
        self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.description.setMinimumHeight(40)
        self.description.setWordWrap(True)
        layout.addWidget(self.description)

        self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
        self.button_box.rejected.connect(self.reject)
        self.finished.connect(self._finished)
        self.install_button = self.button_box.addButton(
            _('&Install'), QDialogButtonBox.AcceptRole)
        self.install_button.setToolTip(_('Install the selected plugin'))
        self.install_button.clicked.connect(self._install_clicked)
        self.install_button.setEnabled(False)
        self.configure_button = self.button_box.addButton(
            ' ' + _('&Customize plugin ') + ' ', QDialogButtonBox.ResetRole)
        self.configure_button.setToolTip(
            _('Customize the options for this plugin'))
        self.configure_button.clicked.connect(self._configure_clicked)
        self.configure_button.setEnabled(False)
        layout.addWidget(self.button_box)

    def update_forum_label(self):
        txt = ''
        if self.forum_link:
            txt = '<a href="%s">%s</a>' % (self.forum_link,
                                           self.forum_label_text)
        self.forum_label.setText(txt)

    def _create_context_menu(self):
        self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.install_action = QAction(
            QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self)
        self.install_action.setToolTip(_('Install the selected plugin'))
        self.install_action.triggered.connect(self._install_clicked)
        self.install_action.setEnabled(False)
        self.plugin_view.addAction(self.install_action)
        self.history_action = QAction(QIcon(I('chapters.png')),
                                      _('Version &History'), self)
        self.history_action.setToolTip(
            _('Show history of changes to this plugin'))
        self.history_action.triggered.connect(self._history_clicked)
        self.history_action.setEnabled(False)
        self.plugin_view.addAction(self.history_action)
        self.forum_action = QAction(QIcon(I('plugins/mobileread.png')),
                                    _('Plugin &Forum Thread'), self)
        self.forum_action.triggered.connect(self._forum_label_activated)
        self.forum_action.setEnabled(False)
        self.plugin_view.addAction(self.forum_action)

        sep1 = QAction(self)
        sep1.setSeparator(True)
        self.plugin_view.addAction(sep1)

        self.toggle_enabled_action = QAction(_('Enable/&Disable plugin'), self)
        self.toggle_enabled_action.setToolTip(
            _('Enable or disable this plugin'))
        self.toggle_enabled_action.triggered.connect(
            self._toggle_enabled_clicked)
        self.toggle_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.toggle_enabled_action)
        self.uninstall_action = QAction(_('&Remove plugin'), self)
        self.uninstall_action.setToolTip(_('Uninstall the selected plugin'))
        self.uninstall_action.triggered.connect(self._uninstall_clicked)
        self.uninstall_action.setEnabled(False)
        self.plugin_view.addAction(self.uninstall_action)

        sep2 = QAction(self)
        sep2.setSeparator(True)
        self.plugin_view.addAction(sep2)

        self.donate_enabled_action = QAction(QIcon(I('donate.png')),
                                             _('Donate to developer'), self)
        self.donate_enabled_action.setToolTip(
            _('Donate to the developer of this plugin'))
        self.donate_enabled_action.triggered.connect(self._donate_clicked)
        self.donate_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.donate_enabled_action)

        sep3 = QAction(self)
        sep3.setSeparator(True)
        self.plugin_view.addAction(sep3)

        self.configure_action = QAction(QIcon(I('config.png')),
                                        _('&Customize plugin'), self)
        self.configure_action.setToolTip(
            _('Customize the options for this plugin'))
        self.configure_action.triggered.connect(self._configure_clicked)
        self.configure_action.setEnabled(False)
        self.plugin_view.addAction(self.configure_action)

    def _finished(self, *args):
        if self.model:
            update_plugins = filter(filter_upgradeable_plugins,
                                    self.model.display_plugins)
            self.gui.recalc_update_label(len(update_plugins))

    def _plugin_current_changed(self, current, previous):
        if current.isValid():
            actual_idx = self.proxy_model.mapToSource(current)
            display_plugin = self.model.display_plugins[actual_idx.row()]
            self.description.setText(display_plugin.description)
            self.forum_link = display_plugin.forum_link
            self.zip_url = display_plugin.zip_url
            self.forum_action.setEnabled(bool(self.forum_link))
            self.install_button.setEnabled(
                display_plugin.is_valid_to_install())
            self.install_action.setEnabled(self.install_button.isEnabled())
            self.uninstall_action.setEnabled(display_plugin.is_installed())
            self.history_action.setEnabled(display_plugin.has_changelog)
            self.configure_button.setEnabled(display_plugin.is_installed())
            self.configure_action.setEnabled(self.configure_button.isEnabled())
            self.toggle_enabled_action.setEnabled(
                display_plugin.is_installed())
            self.donate_enabled_action.setEnabled(
                bool(display_plugin.donation_link))
        else:
            self.description.setText('')
            self.forum_link = None
            self.zip_url = None
            self.forum_action.setEnabled(False)
            self.install_button.setEnabled(False)
            self.install_action.setEnabled(False)
            self.uninstall_action.setEnabled(False)
            self.history_action.setEnabled(False)
            self.configure_button.setEnabled(False)
            self.configure_action.setEnabled(False)
            self.toggle_enabled_action.setEnabled(False)
            self.donate_enabled_action.setEnabled(False)
        self.update_forum_label()

    def _donate_clicked(self):
        plugin = self._selected_display_plugin()
        if plugin and plugin.donation_link:
            open_url(QUrl(plugin.donation_link))

    def _select_and_focus_view(self, change_selection=True):
        if change_selection and self.plugin_view.model().rowCount() > 0:
            self.plugin_view.selectRow(0)
        else:
            idx = self.plugin_view.selectionModel().currentIndex()
            self._plugin_current_changed(idx, 0)
        self.plugin_view.setFocus()

    def _filter_combo_changed(self, idx):
        self.filter_by_name_lineedit.setText(
            ""
        )  # clear the name filter text when a different group was selected
        self.proxy_model.set_filter_criteria(idx)
        if idx == FILTER_NOT_INSTALLED:
            self.plugin_view.sortByColumn(5, Qt.DescendingOrder)
        else:
            self.plugin_view.sortByColumn(0, Qt.AscendingOrder)
        self._select_and_focus_view()

    def _filter_name_lineedit_changed(self, text):
        self.proxy_model.set_filter_text(
            text)  # set the filter text for filterAcceptsRow

    def _forum_label_activated(self):
        if self.forum_link:
            open_url(QUrl(self.forum_link))

    def _selected_display_plugin(self):
        idx = self.plugin_view.selectionModel().currentIndex()
        actual_idx = self.proxy_model.mapToSource(idx)
        return self.model.display_plugins[actual_idx.row()]

    def _uninstall_plugin(self, name_to_remove):
        if DEBUG:
            prints('Removing plugin: ', name_to_remove)
        remove_plugin(name_to_remove)
        # Make sure that any other plugins that required this plugin
        # to be uninstalled first have the requirement removed
        for display_plugin in self.model.display_plugins:
            # Make sure we update the status and display of the
            # plugin we just uninstalled
            if name_to_remove in display_plugin.uninstall_plugins:
                if DEBUG:
                    prints('Removing uninstall dependency for: ',
                           display_plugin.name)
                display_plugin.uninstall_plugins.remove(name_to_remove)
            if display_plugin.name == name_to_remove:
                if DEBUG:
                    prints('Resetting plugin to uninstalled status: ',
                           display_plugin.name)
                display_plugin.installed_version = None
                display_plugin.plugin = None
                display_plugin.uninstall_plugins = []
                if self.proxy_model.filter_criteria not in [
                        FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE
                ]:
                    self.model.refresh_plugin(display_plugin)

    def _uninstall_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(
                self,
                _('Are you sure?'),
                '<p>' +
                _('Are you sure you want to uninstall the <b>%s</b> plugin?') %
                display_plugin.name,
                show_copy_button=False):
            return
        self._uninstall_plugin(display_plugin.name)
        if self.proxy_model.filter_criteria in [
                FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE
        ]:
            self.model.beginResetModel(), self.model.endResetModel()
            self._select_and_focus_view()
        else:
            self._select_and_focus_view(change_selection=False)

    def _install_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(
                self,
                _('Install %s') % display_plugin.name,
                '<p>' +
                _('Installing plugins is a <b>security risk</b>. '
                  'Plugins can contain a virus/malware. '
                  'Only install it if you got it from a trusted source.'
                  ' Are you sure you want to proceed?'),
                show_copy_button=False):
            return

        if display_plugin.uninstall_plugins:
            uninstall_names = list(display_plugin.uninstall_plugins)
            if DEBUG:
                prints('Uninstalling plugin: ', ', '.join(uninstall_names))
            for name_to_remove in uninstall_names:
                self._uninstall_plugin(name_to_remove)

        plugin_zip_url = display_plugin.zip_url
        if DEBUG:
            prints('Downloading plugin zip attachment: ', plugin_zip_url)
        self.gui.status_bar.showMessage(
            _('Downloading plugin zip attachment: %s') % plugin_zip_url)
        zip_path = self._download_zip(plugin_zip_url)

        if DEBUG:
            prints('Installing plugin: ', zip_path)
        self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path)

        do_restart = False
        try:
            from calibre.customize.ui import config
            installed_plugins = frozenset(config['plugins'])
            try:
                plugin = add_plugin(zip_path)
            except NameConflict as e:
                return error_dialog(self.gui,
                                    _('Already exists'),
                                    unicode(e),
                                    show=True)
            # Check for any toolbars to add to.
            widget = ConfigWidget(self.gui)
            widget.gui = self.gui
            widget.check_for_add_to_toolbars(plugin,
                                             previously_installed=plugin.name
                                             in installed_plugins)
            self.gui.status_bar.showMessage(
                _('Plugin installed: %s') % display_plugin.name)
            d = info_dialog(
                self.gui,
                _('Success'),
                _('Plugin <b>{0}</b> successfully installed under <b>'
                  ' {1} plugins</b>. You may have to restart calibre '
                  'for the plugin to take effect.').format(
                      plugin.name, plugin.type),
                show_copy_button=False)
            b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole)
            b.setIcon(QIcon(I('lt.png')))
            d.do_restart = False

            def rf():
                d.do_restart = True

            b.clicked.connect(rf)
            d.set_details('')
            d.exec_()
            b.clicked.disconnect()
            do_restart = d.do_restart

            display_plugin.plugin = plugin
            # We cannot read the 'actual' version information as the plugin will not be loaded yet
            display_plugin.installed_version = display_plugin.available_version
        except:
            if DEBUG:
                prints('ERROR occurred while installing plugin: %s' %
                       display_plugin.name)
                traceback.print_exc()
            error_dialog(
                self.gui,
                _('Install Plugin Failed'),
                _('A problem occurred while installing this plugin.'
                  ' This plugin will now be uninstalled.'
                  ' Please post the error message in details below into'
                  ' the forum thread for this plugin and restart Calibre.'),
                det_msg=traceback.format_exc(),
                show=True)
            if DEBUG:
                prints('Due to error now uninstalling plugin: %s' %
                       display_plugin.name)
            remove_plugin(display_plugin.name)
            display_plugin.plugin = None

        display_plugin.uninstall_plugins = []
        if self.proxy_model.filter_criteria in [
                FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE
        ]:
            self.model.beginResetModel(), self.model.endResetModel()
            self._select_and_focus_view()
        else:
            self.model.refresh_plugin(display_plugin)
            self._select_and_focus_view(change_selection=False)
        if do_restart:
            self.do_restart = True
            self.accept()

    def _history_clicked(self):
        display_plugin = self._selected_display_plugin()
        text = self._read_version_history_html(display_plugin.forum_link)
        if text:
            dlg = VersionHistoryDialog(self, display_plugin.name, text)
            dlg.exec_()
        else:
            return error_dialog(
                self,
                _('Version history missing'),
                _('Unable to find the version history for %s') %
                display_plugin.name,
                show=True)

    def _configure_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.is_customizable():
            return info_dialog(self,
                               _('Plugin not customizable'),
                               _('Plugin: %s does not need customization') %
                               plugin.name,
                               show=True)
        from calibre.customize import InterfaceActionBase
        if isinstance(plugin, InterfaceActionBase) and not getattr(
                plugin, 'actual_iaction_plugin_loaded', False):
            return error_dialog(self,
                                _('Must restart'),
                                _('You must restart calibre before you can'
                                  ' configure the <b>%s</b> plugin') %
                                plugin.name,
                                show=True)
        plugin.do_user_config(self.parent())

    def _toggle_enabled_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.can_be_disabled:
            return error_dialog(self,
                                _('Plugin cannot be disabled'),
                                _('The plugin: %s cannot be disabled') %
                                plugin.name,
                                show=True)
        if is_disabled(plugin):
            enable_plugin(plugin)
        else:
            disable_plugin(plugin)
        self.model.refresh_plugin(display_plugin)

    def _read_version_history_html(self, forum_link):
        br = browser()
        br.set_handle_gzip(True)
        try:
            raw = br.open_novisit(forum_link).read()
            if not raw:
                return None
        except:
            traceback.print_exc()
            return None
        raw = raw.decode('utf-8', errors='replace')
        root = html.fromstring(raw)
        spoiler_nodes = root.xpath(
            '//div[@class="smallfont" and strong="Spoiler"]')
        for spoiler_node in spoiler_nodes:
            try:
                if spoiler_node.getprevious() is None:
                    # This is a spoiler node that has been indented using [INDENT]
                    # Need to go up to parent div, then previous node to get header
                    heading_node = spoiler_node.getparent().getprevious()
                else:
                    # This is a spoiler node after a BR tag from the heading
                    heading_node = spoiler_node.getprevious().getprevious()
                if heading_node is None:
                    continue
                if heading_node.text_content().lower().find(
                        'version history') != -1:
                    div_node = spoiler_node.xpath('div')[0]
                    text = html.tostring(div_node,
                                         method='html',
                                         encoding=unicode)
                    return re.sub('<div\s.*?>', '<div>', text)
            except:
                if DEBUG:
                    prints('======= MobileRead Parse Error =======')
                    traceback.print_exc()
                    prints(html.tostring(spoiler_node))
        return None

    def _download_zip(self, plugin_zip_url):
        from calibre.ptempfile import PersistentTemporaryFile
        br = browser(user_agent='%s %s' % (__appname__, __version__))
        raw = br.open_novisit(plugin_zip_url).read()
        with PersistentTemporaryFile('.zip') as pt:
            pt.write(raw)
        return pt.name
예제 #7
0
class EditorWidget(QTextEdit, LineEditECM):  # {{{

    data_changed = pyqtSignal()

    @property
    def readonly(self):
        return self.isReadOnly()

    @readonly.setter
    def readonly(self, val):
        self.setReadOnly(bool(val))

    @contextmanager
    def editing_cursor(self, set_cursor=True):
        c = self.textCursor()
        c.beginEditBlock()
        yield c
        c.endEditBlock()
        if set_cursor:
            self.setTextCursor(c)
        self.focus_self()

    def __init__(self, parent=None):
        QTextEdit.__init__(self, parent)
        self.setTabChangesFocus(True)
        self.document().setDefaultStyleSheet(css())
        font = self.font()
        f = QFontInfo(font)
        delta = tweaks['change_book_details_font_size_by'] + 1
        if delta:
            font.setPixelSize(f.pixelSize() + delta)
            self.setFont(font)
        f = QFontMetrics(self.font())
        self.em_size = f.horizontalAdvance('m')
        self.base_url = None
        self._parent = weakref.ref(parent)
        self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL)

        extra_shortcuts = {
            'bold': 'Bold',
            'italic': 'Italic',
            'underline': 'Underline',
        }

        for rec in (
            ('bold', 'format-text-bold', _('Bold'), True),
            ('italic', 'format-text-italic', _('Italic'), True),
            ('underline', 'format-text-underline', _('Underline'), True),
            ('strikethrough', 'format-text-strikethrough', _('Strikethrough'), True),
            ('superscript', 'format-text-superscript', _('Superscript'), True),
            ('subscript', 'format-text-subscript', _('Subscript'), True),
            ('ordered_list', 'format-list-ordered', _('Ordered list'), True),
            ('unordered_list', 'format-list-unordered', _('Unordered list'), True),

            ('align_left', 'format-justify-left', _('Align left'), True),
            ('align_center', 'format-justify-center', _('Align center'), True),
            ('align_right', 'format-justify-right', _('Align right'), True),
            ('align_justified', 'format-justify-fill', _('Align justified'), True),
            ('undo', 'edit-undo', _('Undo'), ),
            ('redo', 'edit-redo', _('Redo'), ),
            ('remove_format', 'edit-clear', _('Remove formatting'), ),
            ('copy', 'edit-copy', _('Copy'), ),
            ('paste', 'edit-paste', _('Paste'), ),
            ('paste_and_match_style', 'edit-paste', _('Paste and match style'), ),
            ('cut', 'edit-cut', _('Cut'), ),
            ('indent', 'format-indent-more', _('Increase indentation'), ),
            ('outdent', 'format-indent-less', _('Decrease indentation'), ),
            ('select_all', 'edit-select-all', _('Select all'), ),

            ('color', 'format-text-color', _('Foreground color')),
            ('background', 'format-fill-color', _('Background color')),
            ('insert_link', 'insert-link', _('Insert link or image'),),
            ('insert_hr', 'format-text-hr', _('Insert separator'),),
            ('clear', 'trash', _('Clear')),
        ):
            name, icon, text = rec[:3]
            checkable = len(rec) == 4
            ac = QAction(QIcon(I(icon + '.png')), text, self)
            if checkable:
                ac.setCheckable(checkable)
            setattr(self, 'action_'+name, ac)
            ss = extra_shortcuts.get(name)
            if ss is not None:
                ac.setShortcut(QKeySequence(getattr(QKeySequence, ss)))
            ac.triggered.connect(getattr(self, 'do_' + name))

        self.action_block_style = QAction(QIcon(I('format-text-heading.png')),
                _('Style text block'), self)
        self.action_block_style.setToolTip(
                _('Style the selected text block'))
        self.block_style_menu = QMenu(self)
        self.action_block_style.setMenu(self.block_style_menu)
        self.block_style_actions = []
        h = _('Heading {0}')
        for text, name in (
            (_('Normal'), 'p'),
            (h.format(1), 'h1'),
            (h.format(2), 'h2'),
            (h.format(3), 'h3'),
            (h.format(4), 'h4'),
            (h.format(5), 'h5'),
            (h.format(6), 'h6'),
            (_('Blockquote'), 'blockquote'),
        ):
            ac = QAction(text, self)
            self.block_style_menu.addAction(ac)
            ac.block_name = name
            ac.setCheckable(True)
            self.block_style_actions.append(ac)
            ac.triggered.connect(self.do_format_block)

        self.setHtml('')
        self.copyAvailable.connect(self.update_clipboard_actions)
        self.update_clipboard_actions(False)
        self.selectionChanged.connect(self.update_selection_based_actions)
        self.update_selection_based_actions()
        connect_lambda(self.undoAvailable, self, lambda self, yes: self.action_undo.setEnabled(yes))
        connect_lambda(self.redoAvailable, self, lambda self, yes: self.action_redo.setEnabled(yes))
        self.action_undo.setEnabled(False), self.action_redo.setEnabled(False)
        self.textChanged.connect(self.update_cursor_position_actions)
        self.cursorPositionChanged.connect(self.update_cursor_position_actions)
        self.textChanged.connect(self.data_changed)
        self.update_cursor_position_actions()

    def update_clipboard_actions(self, copy_available):
        self.action_copy.setEnabled(copy_available)
        self.action_cut.setEnabled(copy_available)

    def update_selection_based_actions(self):
        pass

    def update_cursor_position_actions(self):
        c = self.textCursor()
        ls = c.currentList()
        self.action_ordered_list.setChecked(ls is not None and ls.format().style() == QTextListFormat.ListDecimal)
        self.action_unordered_list.setChecked(ls is not None and ls.format().style() == QTextListFormat.ListDisc)
        tcf = c.charFormat()
        vert = tcf.verticalAlignment()
        self.action_superscript.setChecked(vert == QTextCharFormat.AlignSuperScript)
        self.action_subscript.setChecked(vert == QTextCharFormat.AlignSubScript)
        self.action_bold.setChecked(tcf.fontWeight() == QFont.Bold)
        self.action_italic.setChecked(tcf.fontItalic())
        self.action_underline.setChecked(tcf.fontUnderline())
        self.action_strikethrough.setChecked(tcf.fontStrikeOut())
        bf = c.blockFormat()
        a = bf.alignment()
        self.action_align_left.setChecked(a == Qt.AlignLeft)
        self.action_align_right.setChecked(a == Qt.AlignRight)
        self.action_align_center.setChecked(a == Qt.AlignHCenter)
        self.action_align_justified.setChecked(a == Qt.AlignJustify)
        lvl = bf.headingLevel()
        name = 'p'
        if lvl == 0:
            if bf.leftMargin() == bf.rightMargin() and bf.leftMargin() > 0:
                name = 'blockquote'
        else:
            name = 'h{}'.format(lvl)
        for ac in self.block_style_actions:
            ac.setChecked(ac.block_name == name)

    def set_readonly(self, what):
        self.readonly = what

    def focus_self(self):
        self.setFocus(Qt.TabFocusReason)

    def do_clear(self, *args):
        c = self.textCursor()
        c.beginEditBlock()
        c.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor)
        c.movePosition(QTextCursor.End, QTextCursor.KeepAnchor)
        c.removeSelectedText()
        c.endEditBlock()
        self.focus_self()
    clear_text = do_clear

    def do_bold(self):
        with self.editing_cursor() as c:
            fmt = QTextCharFormat()
            fmt.setFontWeight(
                QFont.Bold if c.charFormat().fontWeight() != QFont.Bold else QFont.Normal)
            c.mergeCharFormat(fmt)

    def do_italic(self):
        with self.editing_cursor() as c:
            fmt = QTextCharFormat()
            fmt.setFontItalic(not c.charFormat().fontItalic())
            c.mergeCharFormat(fmt)

    def do_underline(self):
        with self.editing_cursor() as c:
            fmt = QTextCharFormat()
            fmt.setFontUnderline(not c.charFormat().fontUnderline())
            c.mergeCharFormat(fmt)

    def do_strikethrough(self):
        with self.editing_cursor() as c:
            fmt = QTextCharFormat()
            fmt.setFontStrikeOut(not c.charFormat().fontStrikeOut())
            c.mergeCharFormat(fmt)

    def do_vertical_align(self, which):
        with self.editing_cursor() as c:
            fmt = QTextCharFormat()
            fmt.setVerticalAlignment(which)
            c.mergeCharFormat(fmt)

    def do_superscript(self):
        self.do_vertical_align(QTextCharFormat.AlignSuperScript)

    def do_subscript(self):
        self.do_vertical_align(QTextCharFormat.AlignSubScript)

    def do_list(self, fmt):
        with self.editing_cursor() as c:
            ls = c.currentList()
            if ls is not None:
                lf = ls.format()
                if lf.style() == fmt:
                    c.setBlockFormat(QTextBlockFormat())
                else:
                    lf.setStyle(fmt)
                    ls.setFormat(lf)
            else:
                ls = c.createList(fmt)

    def do_ordered_list(self):
        self.do_list(QTextListFormat.ListDecimal)

    def do_unordered_list(self):
        self.do_list(QTextListFormat.ListDisc)

    def do_alignment(self, which):
        with self.editing_cursor() as c:
            fmt = QTextBlockFormat()
            fmt.setAlignment(which)
            c.setBlockFormat(fmt)

    def do_align_left(self):
        self.do_alignment(Qt.AlignLeft)

    def do_align_center(self):
        self.do_alignment(Qt.AlignHCenter)

    def do_align_right(self):
        self.do_alignment(Qt.AlignRight)

    def do_align_justified(self):
        self.do_alignment(Qt.AlignJustify)

    def do_undo(self):
        self.undo()
        self.focus_self()

    def do_redo(self):
        self.redo()
        self.focus_self()

    def do_remove_format(self):
        with self.editing_cursor() as c:
            c.setBlockFormat(QTextBlockFormat())
            c.setCharFormat(QTextCharFormat())

    def do_copy(self):
        self.copy()
        self.focus_self()

    def do_paste(self):
        self.paste()
        self.focus_self()

    def do_paste_and_match_style(self):
        text = QApplication.instance().clipboard().text()
        if text:
            self.setText(text)

    def do_cut(self):
        self.cut()
        self.focus_self()

    def indent_block(self, mult=1):
        with self.editing_cursor() as c:
            bf = c.blockFormat()
            bf.setTextIndent(bf.textIndent() + 2 * self.em_size * mult)
            c.setBlockFormat(bf)

    def do_indent(self):
        self.indent_block()

    def do_outdent(self):
        self.indent_block(-1)

    def do_select_all(self):
        with self.editing_cursor() as c:
            c.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor)
            c.movePosition(QTextCursor.End, QTextCursor.KeepAnchor)

    def level_for_block_type(self, name):
        if name == 'blockquote':
            return 0
        return {q: i for i, q in enumerate('p h1 h2 h3 h4 h5 h6'.split())}[name]

    def do_format_block(self):
        name = self.sender().block_name
        with self.editing_cursor() as c:
            bf = QTextBlockFormat()
            cf = QTextCharFormat()
            bcf = c.blockCharFormat()
            lvl = self.level_for_block_type(name)
            wt = QFont.Bold if lvl else None
            adjust = (0, 3, 2, 1, 0, -1, -1)[lvl]
            pos = None
            if not c.hasSelection():
                pos = c.position()
                c.movePosition(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor)
                c.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor)
            # margin values are taken from qtexthtmlparser.cpp
            hmargin = 0
            if name == 'blockquote':
                hmargin = 40
            tmargin = bmargin = 12
            if name == 'h1':
                tmargin, bmargin = 18, 12
            elif name == 'h2':
                tmargin, bmargin = 16, 12
            elif name == 'h3':
                tmargin, bmargin = 14, 12
            elif name == 'h4':
                tmargin, bmargin = 12, 12
            elif name == 'h5':
                tmargin, bmargin = 12, 4
            bf.setLeftMargin(hmargin), bf.setRightMargin(hmargin)
            bf.setTopMargin(tmargin), bf.setBottomMargin(bmargin)
            bf.setHeadingLevel(lvl)
            if adjust:
                bcf.setProperty(QTextCharFormat.FontSizeAdjustment, adjust)
                cf.setProperty(QTextCharFormat.FontSizeAdjustment, adjust)
            if wt:
                bcf.setProperty(QTextCharFormat.FontWeight, wt)
                cf.setProperty(QTextCharFormat.FontWeight, wt)
            c.setBlockCharFormat(bcf)
            c.mergeCharFormat(cf)
            c.mergeBlockFormat(bf)
            if pos is not None:
                c.setPosition(pos)

    def do_color(self):
        col = QColorDialog.getColor(Qt.black, self,
                _('Choose foreground color'), QColorDialog.ShowAlphaChannel)
        if col.isValid():
            fmt = QTextCharFormat()
            fmt.setForeground(QBrush(col))
            with self.editing_cursor() as c:
                c.mergeCharFormat(fmt)

    def do_background(self):
        col = QColorDialog.getColor(Qt.white, self,
                _('Choose background color'), QColorDialog.ShowAlphaChannel)
        if col.isValid():
            fmt = QTextCharFormat()
            fmt.setBackground(QBrush(col))
            with self.editing_cursor() as c:
                c.mergeCharFormat(fmt)

    def do_insert_hr(self, *args):
        with self.editing_cursor() as c:
            c.movePosition(c.EndOfBlock, c.MoveAnchor)
            c.insertHtml('<hr>')

    def do_insert_link(self, *args):
        link, name, is_image = self.ask_link()
        if not link:
            return
        url = self.parse_link(link)
        if url.isValid():
            url = unicode_type(url.toString(NO_URL_FORMATTING))
            self.focus_self()
            with self.editing_cursor() as c:
                if is_image:
                    c.insertImage(url)
                else:
                    oldfmt = QTextCharFormat(c.charFormat())
                    fmt = QTextCharFormat()
                    fmt.setAnchor(True)
                    fmt.setAnchorHref(url)
                    fmt.setForeground(QBrush(self.palette().color(QPalette.Link)))
                    if name or not c.hasSelection():
                        c.mergeCharFormat(fmt)
                        c.insertText(name or url)
                    else:
                        pos, anchor = c.position(), c.anchor()
                        start, end = min(pos, anchor), max(pos, anchor)
                        for i in range(start, end):
                            cur = self.textCursor()
                            cur.setPosition(i), cur.setPosition(i + 1, c.KeepAnchor)
                            cur.mergeCharFormat(fmt)
                    c.setPosition(c.position())
                    c.setCharFormat(oldfmt)

        else:
            error_dialog(self, _('Invalid URL'),
                         _('The url %r is invalid') % link, show=True)

    def ask_link(self):

        class Ask(QDialog):

            def accept(self):
                if self.treat_as_image.isChecked():
                    url = self.url.text()
                    if url.lower().split(':', 1)[0] in ('http', 'https'):
                        error_dialog(self, _('Remote images not supported'), _(
                            'You must download the image to your computer, URLs pointing'
                            ' to remote images are not supported.'), show=True)
                        return
                QDialog.accept(self)

        d = Ask(self)
        d.setWindowTitle(_('Create link'))
        l = QFormLayout()
        l.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
        d.setLayout(l)
        d.url = QLineEdit(d)
        d.name = QLineEdit(d)
        d.treat_as_image = QCheckBox(d)
        d.setMinimumWidth(600)
        d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        d.br = b = QPushButton(_('&Browse'))
        b.setIcon(QIcon(I('document_open.png')))

        def cf():
            filetypes = []
            if d.treat_as_image.isChecked():
                filetypes = [(_('Images'), 'png jpeg jpg gif'.split())]
            files = choose_files(d, 'select link file', _('Choose file'), filetypes, select_only_single_file=True)
            if files:
                path = files[0]
                d.url.setText(path)
                if path and os.path.exists(path):
                    with lopen(path, 'rb') as f:
                        q = what(f)
                    is_image = q in {'jpeg', 'png', 'gif'}
                    d.treat_as_image.setChecked(is_image)

        b.clicked.connect(cf)
        d.la = la = QLabel(_(
            'Enter a URL. If you check the "Treat the URL as an image" box '
            'then the URL will be added as an image reference instead of as '
            'a link. You can also choose to create a link to a file on '
            'your computer. '
            'Note that if you create a link to a file on your computer, it '
            'will stop working if the file is moved.'))
        la.setWordWrap(True)
        la.setStyleSheet('QLabel { margin-bottom: 1.5ex }')
        l.setWidget(0, l.SpanningRole, la)
        l.addRow(_('Enter &URL:'), d.url)
        l.addRow(_('Treat the URL as an &image'), d.treat_as_image)
        l.addRow(_('Enter &name (optional):'), d.name)
        l.addRow(_('Choose a file on your computer:'), d.br)
        l.addRow(d.bb)
        d.bb.accepted.connect(d.accept)
        d.bb.rejected.connect(d.reject)
        d.resize(d.sizeHint())
        link, name, is_image = None, None, False
        if d.exec_() == d.Accepted:
            link, name = unicode_type(d.url.text()).strip(), unicode_type(d.name.text()).strip()
            is_image = d.treat_as_image.isChecked()
        return link, name, is_image

    def parse_link(self, link):
        link = link.strip()
        if link and os.path.exists(link):
            return QUrl.fromLocalFile(link)
        has_schema = re.match(r'^[a-zA-Z]+:', link)
        if has_schema is not None:
            url = QUrl(link, QUrl.TolerantMode)
            if url.isValid():
                return url
        if os.path.exists(link):
            return QUrl.fromLocalFile(link)

        if has_schema is None:
            first, _, rest = link.partition('.')
            prefix = 'http'
            if first == 'ftp':
                prefix = 'ftp'
            url = QUrl(prefix +'://'+link, QUrl.TolerantMode)
            if url.isValid():
                return url

        return QUrl(link, QUrl.TolerantMode)

    def sizeHint(self):
        return QSize(150, 150)

    @property
    def html(self):
        raw = original_html = self.toHtml()
        check = self.toPlainText().strip()
        raw = xml_to_unicode(raw, strip_encoding_pats=True, resolve_entities=True)[0]
        raw = self.comments_pat.sub('', raw)
        if not check and '<img' not in raw.lower():
            return ''

        root = parse(raw, maybe_xhtml=False, sanitize_names=True)
        if root.xpath('//meta[@name="calibre-dont-sanitize"]'):
            # Bypass cleanup if special meta tag exists
            return original_html

        try:
            cleanup_qt_markup(root)
        except Exception:
            import traceback
            traceback.print_exc()
        elems = []
        for body in root.xpath('//body'):
            if body.text:
                elems.append(body.text)
            elems += [html.tostring(x, encoding='unicode') for x in body if
                x.tag not in ('script', 'style')]

        if len(elems) > 1:
            ans = '<div>%s</div>'%(u''.join(elems))
        else:
            ans = ''.join(elems)
            if not ans.startswith('<'):
                ans = '<p>%s</p>'%ans
        return xml_replace_entities(ans)

    @html.setter
    def html(self, val):
        self.setHtml(val)

    def set_base_url(self, qurl):
        self.base_url = qurl

    @pyqtSlot(int, 'QUrl', result='QVariant')
    def loadResource(self, rtype, qurl):
        if self.base_url:
            if qurl.isRelative():
                qurl = self.base_url.resolved(qurl)
            if qurl.isLocalFile():
                path = qurl.toLocalFile()
                try:
                    with lopen(path, 'rb') as f:
                        data = f.read()
                except EnvironmentError:
                    pass
                else:
                    return QByteArray(data)

    def set_html(self, val, allow_undo=True):
        if not allow_undo or self.readonly:
            self.html = val
            return
        with self.editing_cursor() as c:
            c.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor)
            c.movePosition(QTextCursor.End, QTextCursor.KeepAnchor)
            c.removeSelectedText()
            c.insertHtml(val)

    def text(self):
        return self.textCursor().selectedText()

    def setText(self, text):
        with self.editing_cursor() as c:
            c.insertText(text)

    def contextMenuEvent(self, ev):
        menu = self.createStandardContextMenu()
        for action in menu.actions():
            parts = action.text().split('\t')
            if len(parts) == 2 and QKeySequence(QKeySequence.Paste).toString(QKeySequence.NativeText) in parts[-1]:
                menu.insertAction(action, self.action_paste_and_match_style)
                break
        else:
            menu.addAction(self.action_paste_and_match_style)
        st = self.text()
        m = QMenu(_('Fonts'))
        m.addAction(self.action_bold), m.addAction(self.action_italic), m.addAction(self.action_underline)
        menu.addMenu(m)

        if st and st.strip():
            self.create_change_case_menu(menu)
        parent = self._parent()
        if hasattr(parent, 'toolbars_visible'):
            vis = parent.toolbars_visible
            menu.addAction(_('%s toolbars') % (_('Hide') if vis else _('Show')), parent.toggle_toolbars)
        menu.exec_(ev.globalPos())
예제 #8
0
class EditorWidget(QWebView):  # {{{

    def __init__(self, parent=None):
        QWebView.__init__(self, parent)
        self._parent = weakref.ref(parent)
        self.readonly = False

        self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL)

        extra_shortcuts = {
                'ToggleBold': 'Bold',
                'ToggleItalic': 'Italic',
                'ToggleUnderline': 'Underline',
        }

        for wac, name, icon, text, checkable in [
                ('ToggleBold', 'bold', 'format-text-bold', _('Bold'), True),
                ('ToggleItalic', 'italic', 'format-text-italic', _('Italic'),
                    True),
                ('ToggleUnderline', 'underline', 'format-text-underline',
                    _('Underline'), True),
                ('ToggleStrikethrough', 'strikethrough', 'format-text-strikethrough',
                    _('Strikethrough'), True),
                ('ToggleSuperscript', 'superscript', 'format-text-superscript',
                    _('Superscript'), True),
                ('ToggleSubscript', 'subscript', 'format-text-subscript',
                    _('Subscript'), True),
                ('InsertOrderedList', 'ordered_list', 'format-list-ordered',
                    _('Ordered list'), True),
                ('InsertUnorderedList', 'unordered_list', 'format-list-unordered',
                    _('Unordered list'), True),

                ('AlignLeft', 'align_left', 'format-justify-left',
                    _('Align left'), False),
                ('AlignCenter', 'align_center', 'format-justify-center',
                    _('Align center'), False),
                ('AlignRight', 'align_right', 'format-justify-right',
                    _('Align right'), False),
                ('AlignJustified', 'align_justified', 'format-justify-fill',
                    _('Align justified'), False),
                ('Undo', 'undo', 'edit-undo', _('Undo'), False),
                ('Redo', 'redo', 'edit-redo', _('Redo'), False),
                ('RemoveFormat', 'remove_format', 'trash', _('Remove formatting'), False),
                ('Copy', 'copy', 'edit-copy', _('Copy'), False),
                ('Paste', 'paste', 'edit-paste', _('Paste'), False),
                ('Cut', 'cut', 'edit-cut', _('Cut'), False),
                ('Indent', 'indent', 'format-indent-more',
                    _('Increase Indentation'), False),
                ('Outdent', 'outdent', 'format-indent-less',
                    _('Decrease Indentation'), False),
                ('SelectAll', 'select_all', 'edit-select-all',
                    _('Select all'), False),
            ]:
            ac = PageAction(wac, icon, text, checkable, self)
            setattr(self, 'action_'+name, ac)
            ss = extra_shortcuts.get(wac, None)
            if ss:
                ac.setShortcut(QKeySequence(getattr(QKeySequence, ss)))
            if wac == 'RemoveFormat':
                ac.triggered.connect(self.remove_format_cleanup,
                        type=Qt.QueuedConnection)

        self.action_color = QAction(QIcon(I('format-text-color.png')), _('Foreground color'),
                self)
        self.action_color.triggered.connect(self.foreground_color)

        self.action_background = QAction(QIcon(I('format-fill-color.png')),
                _('Background color'), self)
        self.action_background.triggered.connect(self.background_color)

        self.action_block_style = QAction(QIcon(I('format-text-heading.png')),
                _('Style text block'), self)
        self.action_block_style.setToolTip(
                _('Style the selected text block'))
        self.block_style_menu = QMenu(self)
        self.action_block_style.setMenu(self.block_style_menu)
        self.block_style_actions = []
        for text, name in [
                (_('Normal'), 'p'),
                (_('Heading') +' 1', 'h1'),
                (_('Heading') +' 2', 'h2'),
                (_('Heading') +' 3', 'h3'),
                (_('Heading') +' 4', 'h4'),
                (_('Heading') +' 5', 'h5'),
                (_('Heading') +' 6', 'h6'),
                (_('Pre-formatted'), 'pre'),
                (_('Blockquote'), 'blockquote'),
                (_('Address'), 'address'),
                ]:
            ac = BlockStyleAction(text, name, self)
            self.block_style_menu.addAction(ac)
            self.block_style_actions.append(ac)

        self.action_insert_link = QAction(QIcon(I('insert-link.png')),
                _('Insert link or image'), self)
        self.action_insert_link.triggered.connect(self.insert_link)
        self.pageAction(QWebPage.ToggleBold).changed.connect(self.update_link_action)
        self.action_insert_link.setEnabled(False)
        self.action_clear = QAction(QIcon(I('edit-clear.png')), _('Clear'), self)
        self.action_clear.triggered.connect(self.clear_text)

        self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.page().linkClicked.connect(self.link_clicked)

        self.setHtml('')
        self.set_readonly(False)

    def update_link_action(self):
        wac = self.pageAction(QWebPage.ToggleBold)
        self.action_insert_link.setEnabled(wac.isEnabled())

    def set_readonly(self, what):
        self.readonly = what
        self.page().setContentEditable(not self.readonly)

    def clear_text(self, *args):
        us = self.page().undoStack()
        us.beginMacro('clear all text')
        self.action_select_all.trigger()
        self.action_remove_format.trigger()
        self.exec_command('delete')
        us.endMacro()
        self.set_font_style()
        self.setFocus(Qt.OtherFocusReason)

    def link_clicked(self, url):
        open_url(url)

    def foreground_color(self):
        col = QColorDialog.getColor(Qt.black, self,
                _('Choose foreground color'), QColorDialog.ShowAlphaChannel)
        if col.isValid():
            self.exec_command('foreColor', unicode(col.name()))

    def background_color(self):
        col = QColorDialog.getColor(Qt.white, self,
                _('Choose background color'), QColorDialog.ShowAlphaChannel)
        if col.isValid():
            self.exec_command('hiliteColor', unicode(col.name()))

    def insert_link(self, *args):
        link, name, is_image = self.ask_link()
        if not link:
            return
        url = self.parse_link(link)
        if url.isValid():
            url = unicode(url.toString(QUrl.None))
            self.setFocus(Qt.OtherFocusReason)
            if is_image:
                self.exec_command('insertHTML',
                        '<img src="%s" alt="%s"></img>'%(prepare_string_for_xml(url, True),
                            prepare_string_for_xml(name or _('Image'), True)))
            elif name:
                self.exec_command('insertHTML',
                        '<a href="%s">%s</a>'%(prepare_string_for_xml(url, True),
                            prepare_string_for_xml(name)))
            else:
                self.exec_command('createLink', url)
        else:
            error_dialog(self, _('Invalid URL'),
                         _('The url %r is invalid') % link, show=True)

    def ask_link(self):
        d = QDialog(self)
        d.setWindowTitle(_('Create link'))
        l = QFormLayout()
        d.setLayout(l)
        d.url = QLineEdit(d)
        d.name = QLineEdit(d)
        d.treat_as_image = QCheckBox(d)
        d.setMinimumWidth(600)
        d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        d.br = b = QPushButton(_('&Browse'))
        b.setIcon(QIcon(I('document_open.png')))
        def cf():
            files = choose_files(d, 'select link file', _('Choose file'), select_only_single_file=True)
            if files:
                path = files[0]
                d.url.setText(path)
                if path and os.path.exists(path):
                    with lopen(path, 'rb') as f:
                        q = what(f)
                    is_image = q in {'jpeg', 'png', 'gif'}
                    d.treat_as_image.setChecked(is_image)

        b.clicked.connect(cf)
        d.la = la = QLabel(_(
            'Enter a URL. If you check the "Treat the URL as an image" box '
            'then the URL will be added as an image reference instead of as '
            'a link. You can also choose to create a link to a file on '
            'your computer. '
            'Note that if you create a link to a file on your computer, it '
            'will stop working if the file is moved.'))
        la.setWordWrap(True)
        la.setStyleSheet('QLabel { margin-bottom: 1.5ex }')
        l.setWidget(0, l.SpanningRole, la)
        l.addRow(_('Enter &URL:'), d.url)
        l.addRow(_('Treat the URL as an &image'), d.treat_as_image)
        l.addRow(_('Enter &name (optional):'), d.name)
        l.addRow(_('Choose a file on your computer:'), d.br)
        l.addRow(d.bb)
        d.bb.accepted.connect(d.accept)
        d.bb.rejected.connect(d.reject)
        d.resize(d.sizeHint())
        link, name, is_image = None, None, False
        if d.exec_() == d.Accepted:
            link, name = unicode(d.url.text()).strip(), unicode(d.name.text()).strip()
            is_image = d.treat_as_image.isChecked()
        return link, name, is_image

    def parse_link(self, link):
        link = link.strip()
        if link and os.path.exists(link):
            return QUrl.fromLocalFile(link)
        has_schema = re.match(r'^[a-zA-Z]+:', link)
        if has_schema is not None:
            url = QUrl(link, QUrl.TolerantMode)
            if url.isValid():
                return url
        if os.path.exists(link):
            return QUrl.fromLocalFile(link)

        if has_schema is None:
            first, _, rest = link.partition('.')
            prefix = 'http'
            if first == 'ftp':
                prefix = 'ftp'
            url = QUrl(prefix +'://'+link, QUrl.TolerantMode)
            if url.isValid():
                return url

        return QUrl(link, QUrl.TolerantMode)

    def sizeHint(self):
        return QSize(150, 150)

    def exec_command(self, cmd, arg=None):
        frame = self.page().mainFrame()
        if arg is not None:
            js = 'document.execCommand("%s", false, %s);' % (cmd,
                    json.dumps(unicode(arg)))
        else:
            js = 'document.execCommand("%s", false, null);' % cmd
        frame.evaluateJavaScript(js)

    def remove_format_cleanup(self):
        self.html = self.html

    @dynamic_property
    def html(self):

        def fget(self):
            ans = u''
            try:
                if not self.page().mainFrame().documentElement().findFirst('meta[name="calibre-dont-sanitize"]').isNull():
                    # Bypass cleanup if special meta tag exists
                    return unicode(self.page().mainFrame().toHtml())
                check = unicode(self.page().mainFrame().toPlainText()).strip()
                raw = unicode(self.page().mainFrame().toHtml())
                raw = xml_to_unicode(raw, strip_encoding_pats=True,
                                    resolve_entities=True)[0]
                raw = self.comments_pat.sub('', raw)
                if not check and '<img' not in raw.lower():
                    return ans

                try:
                    root = html.fromstring(raw)
                except:
                    root = fromstring(raw)

                elems = []
                for body in root.xpath('//body'):
                    if body.text:
                        elems.append(body.text)
                    elems += [html.tostring(x, encoding=unicode) for x in body if
                        x.tag not in ('script', 'style')]

                if len(elems) > 1:
                    ans = u'<div>%s</div>'%(u''.join(elems))
                else:
                    ans = u''.join(elems)
                    if not ans.startswith('<'):
                        ans = '<p>%s</p>'%ans
                ans = xml_replace_entities(ans)
            except:
                import traceback
                traceback.print_exc()

            return ans

        def fset(self, val):
            self.setHtml(val)
            self.set_font_style()
        return property(fget=fget, fset=fset)

    def set_html(self, val, allow_undo=True):
        if not allow_undo or self.readonly:
            self.html = val
            return
        mf = self.page().mainFrame()
        mf.evaluateJavaScript('document.execCommand("selectAll", false, null)')
        mf.evaluateJavaScript('document.execCommand("insertHTML", false, %s)' % json.dumps(unicode(val)))
        self.set_font_style()

    def set_font_style(self):
        fi = QFontInfo(QApplication.font(self))
        f  = fi.pixelSize() + 1 + int(tweaks['change_book_details_font_size_by'])
        fam = unicode(fi.family()).strip().replace('"', '')
        if not fam:
            fam = 'sans-serif'
        style = 'font-size: %fpx; font-family:"%s",sans-serif;' % (f, fam)

        # toList() is needed because PyQt on Debian is old/broken
        for body in self.page().mainFrame().documentElement().findAll('body').toList():
            body.setAttribute('style', style)
        self.page().setContentEditable(not self.readonly)

    def event(self, ev):
        if ev.type() in (ev.KeyPress, ev.KeyRelease, ev.ShortcutOverride) and ev.key() in (
                Qt.Key_Tab, Qt.Key_Escape, Qt.Key_Backtab):
            if (ev.key() == Qt.Key_Tab and ev.modifiers() & Qt.ControlModifier and ev.type() == ev.KeyPress):
                self.exec_command('insertHTML', '<span style="white-space:pre">\t</span>')
                ev.accept()
                return True
            ev.ignore()
            return False
        return QWebView.event(self, ev)

    def contextMenuEvent(self, ev):
        menu = self.page().createStandardContextMenu()
        paste = self.pageAction(QWebPage.Paste)
        for action in menu.actions():
            if action == paste:
                menu.insertAction(action, self.pageAction(QWebPage.PasteAndMatchStyle))
        parent = self._parent()
        if hasattr(parent, 'toolbars_visible'):
            vis = parent.toolbars_visible
            menu.addAction(_('%s toolbars') % (_('Hide') if vis else _('Show')),
                           (parent.hide_toolbars if vis else parent.show_toolbars))
        menu.exec_(ev.globalPos())
예제 #9
0
    def __init__(self, astergui, parent=None):
        """
        Create panel.

        Arguments:
            astergui (AsterGui): AsterGui instance.
            parent (Optional[QWidget]): Parent widget.
        """
        super(ParameterPanel,
              self).__init__(parent=parent,
                             name=translate("ParameterPanel", "Edit command"),
                             astergui=astergui)
        self.setPixmap(load_pixmap("as_pic_edit_command.png"))

        self._files_model = astergui.study().dataFilesModel()
        self._unit_model = None

        self._command = None
        self.title = ParameterTitle(self)
        self.title.installEventFilter(self)
        self._name = QLineEdit(self)
        self.views = QStackedWidget(self)
        v_layout = QVBoxLayout(self)
        v_layout.setContentsMargins(0, 0, 0, 0)
        v_layout.setSpacing(5)
        v_layout.addWidget(self.title)
        v_layout.addWidget(HLine(self))

        n_layout = QHBoxLayout()
        v_layout.addLayout(n_layout)
        n_layout.addWidget(QLabel(translate("ParameterPanel", "Name"), self))
        n_layout.addWidget(self._name)
        # force to be a valid identifier + length <= 8
        self._name.setValidator(QRegExpValidator(QRegExp(r"[a-zA-Z]\w{1,7}")))

        # create toolbar
        tbar = QToolBar(self)
        tbar.setToolButtonStyle(Qt.ToolButtonIconOnly)
        # - Edit comment
        edit_comment = QAction(translate("AsterStudy", "Edit &Comment"), self)
        edit_comment.setToolTip(translate("AsterStudy", "Edit comment"))
        edit_comment.setStatusTip(
            translate("AsterStudy", "Edit comment for the "
                      "selected object"))
        edit_comment.setIcon(load_icon("as_pic_edit_comment.png"))
        connect(edit_comment.triggered, self._editComment)
        tbar.addAction(edit_comment)
        # - Switch on/off business-translations
        title = translate("AsterStudy", "Use Business-Oriented Translations")
        self.use_translations = QAction(title, self)
        title = translate("AsterStudy", "Use business-oriented translations")
        self.use_translations.setToolTip(title)
        self.use_translations.setStatusTip(title)
        self.use_translations.setIcon(load_icon("as_pic_use_translations.png"))
        self.use_translations.setCheckable(True)
        if behavior().forced_native_names:
            force = behavior().force_native_names
            self.use_translations.setDisabled(True)
            is_on = not force
        else:
            is_on = behavior().use_business_translations
        Options.use_translations = is_on
        self.use_translations.setChecked(is_on)
        connect(self.use_translations.toggled, self.updateTranslations)
        tbar.addAction(self.use_translations)
        # - Hide unused
        hide_unused = astergui.action(ActionType.HideUnused)
        connect(hide_unused.toggled, self._unusedVisibility)
        tbar.addAction(hide_unused)
        # - What's this
        whats_this = QWhatsThis.createAction(tbar)
        whats_this.setToolTip(translate("AsterStudy", "What's this?"))
        whats_this.setStatusTip(
            translate("AsterStudy", "Show element's description"))
        whats_this.setIcon(load_icon("as_pic_whats_this.png"))
        tbar.addAction(whats_this)
        # - Link to doc
        tbar.addAction(astergui.action(ActionType.LinkToDoc))

        n_layout.addWidget(tbar)

        v_layout.addWidget(self.views)
        self._updateState()
예제 #10
0
class ParameterPanel(EditionWidget, WidgetController):
    """Edition Panel implementation."""
    def __init__(self, astergui, parent=None):
        """
        Create panel.

        Arguments:
            astergui (AsterGui): AsterGui instance.
            parent (Optional[QWidget]): Parent widget.
        """
        super(ParameterPanel,
              self).__init__(parent=parent,
                             name=translate("ParameterPanel", "Edit command"),
                             astergui=astergui)
        self.setPixmap(load_pixmap("as_pic_edit_command.png"))

        self._files_model = astergui.study().dataFilesModel()
        self._unit_model = None

        self._command = None
        self.title = ParameterTitle(self)
        self.title.installEventFilter(self)
        self._name = QLineEdit(self)
        self.views = QStackedWidget(self)
        v_layout = QVBoxLayout(self)
        v_layout.setContentsMargins(0, 0, 0, 0)
        v_layout.setSpacing(5)
        v_layout.addWidget(self.title)
        v_layout.addWidget(HLine(self))

        n_layout = QHBoxLayout()
        v_layout.addLayout(n_layout)
        n_layout.addWidget(QLabel(translate("ParameterPanel", "Name"), self))
        n_layout.addWidget(self._name)
        # force to be a valid identifier + length <= 8
        self._name.setValidator(QRegExpValidator(QRegExp(r"[a-zA-Z]\w{1,7}")))

        # create toolbar
        tbar = QToolBar(self)
        tbar.setToolButtonStyle(Qt.ToolButtonIconOnly)
        # - Edit comment
        edit_comment = QAction(translate("AsterStudy", "Edit &Comment"), self)
        edit_comment.setToolTip(translate("AsterStudy", "Edit comment"))
        edit_comment.setStatusTip(
            translate("AsterStudy", "Edit comment for the "
                      "selected object"))
        edit_comment.setIcon(load_icon("as_pic_edit_comment.png"))
        connect(edit_comment.triggered, self._editComment)
        tbar.addAction(edit_comment)
        # - Switch on/off business-translations
        title = translate("AsterStudy", "Use Business-Oriented Translations")
        self.use_translations = QAction(title, self)
        title = translate("AsterStudy", "Use business-oriented translations")
        self.use_translations.setToolTip(title)
        self.use_translations.setStatusTip(title)
        self.use_translations.setIcon(load_icon("as_pic_use_translations.png"))
        self.use_translations.setCheckable(True)
        if behavior().forced_native_names:
            force = behavior().force_native_names
            self.use_translations.setDisabled(True)
            is_on = not force
        else:
            is_on = behavior().use_business_translations
        Options.use_translations = is_on
        self.use_translations.setChecked(is_on)
        connect(self.use_translations.toggled, self.updateTranslations)
        tbar.addAction(self.use_translations)
        # - Hide unused
        hide_unused = astergui.action(ActionType.HideUnused)
        connect(hide_unused.toggled, self._unusedVisibility)
        tbar.addAction(hide_unused)
        # - What's this
        whats_this = QWhatsThis.createAction(tbar)
        whats_this.setToolTip(translate("AsterStudy", "What's this?"))
        whats_this.setStatusTip(
            translate("AsterStudy", "Show element's description"))
        whats_this.setIcon(load_icon("as_pic_whats_this.png"))
        tbar.addAction(whats_this)
        # - Link to doc
        tbar.addAction(astergui.action(ActionType.LinkToDoc))

        n_layout.addWidget(tbar)

        v_layout.addWidget(self.views)
        self._updateState()

    def unitModel(self):
        """
        Method that get unit model.

        Returns:
            UnitModel: Unit model.
        """
        return self._unit_model

    def command(self):
        """
        Get command being edited.

        Returns:
            Command: Command being edited.
        """
        return self._command

    def setCommand(self, command):
        """
        Set command to edit.

        Arguments:
            command (Command): Command to edit.
        """
        self.clear()
        self._command = command
        if self._command is None:
            self._name.setText("")
        else:
            self._name.setText(self._command.name)
            self._unit_model = UnitModel(command.stage)
            pview = self._createParameterView(ParameterPath(self._command), '')
            pview.view().setItemValue(command.storage)
            hide_unused = self.astergui().action(ActionType.HideUnused)
            pview.setUnusedVisibile(not hide_unused.isChecked())
            self.views.setCurrentWidget(pview)
        self._updateState()

    def currentPath(self):
        """
        Get currently edited parameter path.

        Returns:
            str: currently edited parameter path.
        """
        path = ""
        wid = self.currentParameterView()
        if wid is not None:
            path = wid.path()
        return path

    def isCurrentCommand(self):
        """
        Get true if the currently edited view contains command.

        Returns:
            bool: Current edited command flag
        """
        curpath = self.currentPath()
        return ParameterPath(self.command()).isEqual(curpath)

    def currentParameterView(self):
        """
        Get current parameter view.

        Returns:
           ParameterView: current view.
        """
        return self.views.currentWidget()

    def clear(self):
        """Remove all parameter views."""
        while self.views.count() > 0:
            wid = self.views.widget(0)
            if wid is not None:
                self.views.removeWidget(wid)
                wid.deleteLater()

    def store(self):
        """
        Save data from all parameter views.
        """
        cmd = self.command()
        if cmd is not None:
            with auto_dupl_on(self.astergui().study().activeCase):
                cmd.rename(self._name.text())
                wid = self._viewByPath(ParameterPath(cmd))
                if wid is not None:
                    cmd.init(wid.view().itemValue())

    def requiredButtons(self):
        """
        Return the combination of standard button flags required for this
        widget.

        Returns:
            int: button flags for buttons required for this widget
                 (combination of QDialogButtonBox.StandardButton flags).
        """
        if self.isCurrentCommand():
            return QDialogButtonBox.Ok | QDialogButtonBox.Apply | \
                QDialogButtonBox.Close
        else:
            return QDialogButtonBox.Ok | QDialogButtonBox.Cancel | \
                QDialogButtonBox.Abort

    def isButtonEnabled(self, button):
        """
        Return True if a particular button is enabled.

        Arguments:
            button (QDialogButtonBox.StandardButton): button flag.

        Returns:
            True: that means that all buttons should be enabled.
        """
        return True

    def perform(self, button):
        """
        Perform action on button click. Redefined method from the base class.

        Arguments:
            button (QDialogButtonBox.StandardButton): clicked button flag.
        """
        if button == QDialogButtonBox.Ok:
            self.performOk()
        elif button == QDialogButtonBox.Apply:
            self.performApply()
        elif button == QDialogButtonBox.Abort:
            self.performAbort()
        elif button == QDialogButtonBox.Close or \
                button == QDialogButtonBox.Cancel:
            self.performClose()

    def performOk(self):
        """Called when `Ok` button is clicked in Edition panel."""
        self.performChanges(True)

    def performApply(self):
        """Called when `Apply` button is clicked in Edition panel."""
        self.performChanges(False)

    def performAbort(self):
        """Called when `Abort` button is clicked in Edition panel."""
        pref_mgr = self.astergui().preferencesMgr()
        msg = translate(
            "ParameterPanel", "Command edition will be aborted and "
            "all made changes will be lost. "
            "Do you want to continue?")
        noshow = "parampanel_abort"
        ask = MessageBox.question(self.astergui().mainWindow(),
                                  translate("ParameterPanel", "Abort"),
                                  msg,
                                  QMessageBox.Yes | QMessageBox.No,
                                  QMessageBox.Yes,
                                  noshow=noshow,
                                  prefmgr=pref_mgr)
        if ask == QMessageBox.Yes:
            self.close()
            self.astergui().study().revert()

    def performClose(self):
        """Called when `Cancel` button is clicked in Edition panel."""
        has_modif = self._hasModifications()
        if has_modif:
            pref_mgr = self.astergui().preferencesMgr()
            msg = translate(
                "ParameterPanel",
                "There are some unsaved modifications will be "
                "lost. Do you want to continue?")
            noshow = "parampanel_close"
            ask = MessageBox.question(self.astergui().mainWindow(),
                                      translate("ParameterPanel", "Close"),
                                      msg,
                                      QMessageBox.Yes | QMessageBox.No,
                                      QMessageBox.Yes,
                                      noshow=noshow,
                                      prefmgr=pref_mgr)
            has_modif = ask != QMessageBox.Yes

        if not has_modif:
            self.performDissmis(True)

    def performChanges(self, close=True):
        """
        Validate and store the command into data model.
        """
        wid = self.currentParameterView()
        if wid is not None:
            view = wid.view()
            if view.validate():
                cur_path = self.currentPath()
                if self.isCurrentCommand():
                    self.store()
                    self._files_model.update()
                    if self.astergui() is not None:
                        opname = translate("ParameterPanel", "Edit command")
                        self.astergui().study().commit(opname)
                        self.astergui().update()
                    if close:
                        self.performDissmis(False)
                    msg = translate("ParameterPanel",
                                    "Command '{}' successfully stored")
                    msg = msg.format(self._name.text())
                    self.astergui().showMessage(msg)
                else:
                    child_val = view.itemValue()
                    self._removeCurrentView()
                    curview = self.currentParameterView()
                    subitem = curview.view().findItemByPath(cur_path)
                    if subitem is not None:
                        subitem.setItemValue(child_val)
        self._updateState()
        self.updateButtonStatus()

    def performDissmis(self, revert=True):
        """
        Cancel changes and revert the command changes.
        """
        if self.isCurrentCommand():
            self.close()
            if revert:
                self.astergui().study().revert()
        else:
            self._removeCurrentView()
        self._updateState()
        self.updateButtonStatus()

    def showEvent(self, event):
        """
        Reimplemented for internal reason: updates the title
        depending on read only state, etc.
        """
        title = translate("ParameterPanel", "View command") \
            if self.isReadOnly() else \
            translate("ParameterPanel", "Edit command")
        self.setWindowTitle(title)

        hide_unused = self.astergui().action(ActionType.HideUnused)
        hide_unused.setVisible(True)
        hide_unused.setChecked(self.isReadOnly())

        # update meshview
        meshes = avail_meshes_in_cmd(self.command())
        for i, mesh in enumerate(meshes):
            filename, meshname = get_cmd_mesh(mesh)
            if filename:
                if i > 0:
                    self.meshview().displayMEDFileName(filename, meshname, 1.0,
                                                       False)
                else:
                    self.meshview().displayMEDFileName(filename, meshname, 1.0,
                                                       True)

        super(ParameterPanel, self).showEvent(event)

    def hideEvent(self, event):
        """
        Reimplemented for internal reason: hides "Hide unused" action.
        """
        hide_unused = self.astergui().action(ActionType.HideUnused)
        hide_unused.setVisible(False)
        super(ParameterPanel, self).hideEvent(event)

    def updateTranslations(self):
        """
        Update translations in GUI elements.
        """
        Options.use_translations = self.use_translations.isChecked()
        self._updateState()
        for i in xrange(self.views.count()):
            view = self.views.widget(i)
            view.updateTranslations()

    def eventFilter(self, receiver, event):
        """
        Event filter; processes clicking ln links in What's This window.
        """
        if receiver == self.title and event.type() == QEvent.WhatsThisClicked:
            QDesktopServices.openUrl(QUrl(event.href()))
        return super(ParameterPanel, self).eventFilter(receiver, event)

    def _hasModifications(self):
        curview = self.currentParameterView().view() \
            if self.currentParameterView() is not None else None
        return curview.hasModifications() \
            if curview is not None else False

    def _updateState(self):
        """Update state and current title label."""
        disabled = self.command() is None
        self.setDisabled(disabled)

        if not disabled:
            disabled = self.command().gettype(ConversionLevel.NoFail) is None
        self._name.setDisabled(disabled)

        txt = []
        pview = self.currentParameterView()
        if pview is not None:
            txt = pview.path().names()

        ppath = None
        txt_list = []
        tooltip = ""
        whats_this = ""
        while len(txt) > 0:
            name = txt.pop(0)
            if ppath is None:
                ppath = ParameterPath(self.command(), name=name)
            else:
                ppath = ppath.absolutePath(name)
            if ppath.isInSequence():
                txt_list.append("[" + name + "]")
            elif get_cata_typeid(ppath.keyword()) in (IDS.simp, IDS.fact):
                # translate keyword
                kwtext = Options.translate_command(ppath.command().title, name)
                txt_list.append(kwtext)
            elif get_cata_typeid(ppath.keyword()) == IDS.command:
                # translate command
                translation = Options.translate_command(name)
                txt_list.append(translation)
                if translation != name:
                    wttext = italic(translation) + " ({})".format(bold(name))
                else:
                    wttext = bold(name)
                tooltip = preformat(wttext)
                url = self.astergui().doc_url(name)
                if url:
                    wttext += "&nbsp;"
                    wttext += href(
                        image(CFG.rcfile("as_pic_help.png"),
                              width=20,
                              height=20), url)
                wttext = preformat(wttext)
                docs = CATA.get_command_docstring(name)
                if docs:
                    wttext += "<hr>"
                    wttext += docs
                whats_this = wttext

        self.title.setTitle(txt_list)
        self.title.setToolTip(tooltip)
        self.title.setWhatsThis(whats_this)

    def _removeCurrentView(self):
        """
        Remove the parameter view for given object.

        Arguments:
            obj (Parameter): Command's parameter.
        """
        curview = self.currentParameterView()
        if curview is not None:
            master = curview.view().masterItem()
            if master is not None and master.slaveItem() == curview.view():
                master.setSlaveItem(None)
            curview.view().setMasterItem(None)
            view = self._parentView(curview)
            if view is not None:
                self.views.setCurrentWidget(view)
                hide_unused = self.astergui().action(ActionType.HideUnused)
                view.setUnusedVisibile(not hide_unused.isChecked())
            self.views.removeWidget(curview)
            curview.deleteLater()
        self._updateState()

    def _viewByPath(self, path):
        view = None
        for i in xrange(self.views.count()):
            the_view = self.views.widget(i)
            if the_view.path().isEqual(path):
                view = the_view
                break
        return view

    def _parentView(self, curview):
        view = None
        path = curview.path()
        while path is not None and view is None:
            path = path.parentPath()
            view = self._viewByPath(path)
        return view

    def _gotoParameter(self, path, link):
        """
        Activate the parameter view for object with given id.

        Arguments:
            uid (int): Object's UID.
        """
        curview = self.currentParameterView()
        act_item = curview.view().findItemByPath(path)
        child_val = None
        wid = self._createParameterView(path, link)
        if act_item is not None:
            child_val = act_item.itemValue()
            act_item.setSlaveItem(wid.view())
        wid.view().setMasterItem(act_item)
        hide_unused = self.astergui().action(ActionType.HideUnused)
        wid.setUnusedVisibile(not hide_unused.isChecked())
        self.views.setCurrentWidget(wid)
        wid.view().setItemValue(child_val)
        self._updateState()
        self.updateButtonStatus()

    def _createParameterView(self, path, link):
        """
        Create parameter view for given object.

        Arguments:
            path (ParameterPath): Path of parameter to edit.

        Returns:
            ParameterWindow: Parameter view for parameter path.
        """
        # pragma pylint: disable=redefined-variable-type
        pview = None
        if link == EditorLink.Table:
            pview = ParameterTableWindow(path, self, self.views)
        elif link == EditorLink.List:
            pview = ParameterListWindow(path, self, self.views)
        elif link == EditorLink.GrMa:
            pview = ParameterMeshGroupWindow(path, self, self.views)
        else:
            pview = ParameterFactWindow(path, self, self.views)
        connect(pview.gotoParameter, self._gotoParameter)
        self.views.addWidget(pview)
        return pview

    def _unusedVisibility(self, ison):
        """
        Invoked when 'Hide unused' button toggled
        """
        curview = self.currentParameterView()
        curview.setUnusedVisibile(not ison)

    def meshview(self):
        """
        Returns the central *MeshView* object
        """
        return self.astergui().workSpace().panels[Panel.View]

    def _editComment(self):
        """
        Invoked when 'Edit comment' button is clicked
        """
        panel = CommentPanel(self.astergui(), owner=self)
        panel.node = self.command()
        self.astergui().workSpace().panel(Panel.Edit).setEditor(panel)

    def pendingStorage(self):
        """
        Dictionnary being filled as this command is edited.
        """
        wid = self._viewByPath(ParameterPath(self.command()))
        if wid is not None:
            return wid.view().itemValue()
        return None
예제 #11
0
    def genesis(self):
        md = self.qaction.menu()
        cm = partial(self.create_menu_action, md)
        cm('individual',
           _('Edit metadata individually'),
           icon=self.qaction.icon(),
           triggered=partial(self.edit_metadata, False, bulk=False))
        cm('bulk',
           _('Edit metadata in bulk'),
           triggered=partial(self.edit_metadata, False, bulk=True))
        md.addSeparator()
        cm('download',
           _('Download metadata and covers'),
           triggered=partial(self.download_metadata, ids=None),
           shortcut='Ctrl+D')
        self.metadata_menu = md

        self.metamerge_menu = mb = QMenu()
        cm2 = partial(self.create_menu_action, mb)
        cm2('merge delete',
            _('Merge into first selected book - delete others'),
            triggered=self.merge_books)
        mb.addSeparator()
        cm2('merge keep',
            _('Merge into first selected book - keep others'),
            triggered=partial(self.merge_books, safe_merge=True),
            shortcut='Alt+M')
        mb.addSeparator()
        cm2('merge formats',
            _('Merge only formats into first selected book - delete others'),
            triggered=partial(self.merge_books, merge_only_formats=True),
            shortcut='Alt+Shift+M')
        self.merge_menu = mb
        md.addSeparator()
        self.action_copy = cm('copy',
                              _('Copy metadata'),
                              icon='edit-copy.png',
                              triggered=self.copy_metadata)
        self.action_paset = cm('paste',
                               _('Paste metadata'),
                               icon='edit-paste.png',
                               triggered=self.paste_metadata)
        self.action_merge = cm('merge',
                               _('Merge book records'),
                               icon='merge_books.png',
                               shortcut=_('M'),
                               triggered=self.merge_books)
        self.action_merge.setMenu(mb)

        self.qaction.triggered.connect(self.edit_metadata)
        ac = QAction(_('Copy URL to show book in calibre'), self.gui)
        ac.setToolTip(
            _('Copy URLs to show the currently selected books in calibre, to the system clipboard'
              ))
        ac.triggered.connect(self.copy_show_link)
        self.gui.addAction(ac)
        self.gui.keyboard.register_shortcut(self.unique_name + ' - ' +
                                            'copy_show_book',
                                            ac.text(),
                                            description=ac.toolTip(),
                                            action=ac,
                                            group=self.action_spec[0])
        ac = QAction(_('Copy URL to open book in calibre'), self.gui)
        ac.triggered.connect(self.copy_view_link)
        ac.setToolTip(
            _('Copy URLs to open the currently selected books in calibre, to the system clipboard'
              ))
        self.gui.addAction(ac)
        self.gui.keyboard.register_shortcut(self.unique_name + ' - ' +
                                            'copy_view_book',
                                            ac.text(),
                                            description=ac.toolTip(),
                                            action=ac,
                                            group=self.action_spec[0])
예제 #12
0
파일: ui.py 프로젝트: vimior/UF-Debug-Tool
class UFDebugToolUI(object):
    def __init__(self, window=None):
        self.window = window if window is not None else QWidget
        super(UFDebugToolUI, self).__init__()
        self.lang = 'en'
        self.set_ui()

    def set_ui(self):
        self._set_window()
        self._set_menubar()
        self._set_tab()

    def _set_window(self):
        self.window.setWindowTitle(self.window.tr('UF-Debug-Tool'))
        self.window.setMinimumHeight(800)
        self.window.setMinimumWidth(1080)
        self.main_layout = QVBoxLayout(self.window)

    def _set_menubar(self):
        self.menuBar = QMenuBar()
        self.main_layout.setMenuBar(self.menuBar)

        fileMenu = self.menuBar.addMenu('File')
        self.newFileAction = QAction(self.window.tr('New'), self.window)
        self.newFileAction.setShortcut('Ctrl+N')
        self.newFileAction.setStatusTip('New File')
        fileMenu.addAction(self.newFileAction)

        self.openFileAction = QAction(self.window.tr('Open'), self.window)
        self.openFileAction.setShortcut('Ctrl+O')
        self.openFileAction.setToolTip('Open File')
        fileMenu.addAction(self.openFileAction)

        self.saveFileAction = QAction(self.window.tr('Save'), self.window)
        self.saveFileAction.setShortcut('Ctrl+S')
        self.saveFileAction.setStatusTip('Save File')
        fileMenu.addAction(self.saveFileAction)

        self.closeFileAction = QAction(self.window.tr('Close'), self.window)
        self.closeFileAction.setShortcut('Ctrl+W')
        self.closeFileAction.setStatusTip('Close File')
        fileMenu.addAction(self.closeFileAction)

        self.newFileAction.triggered.connect(self.new_dialog)
        self.openFileAction.triggered.connect(self.open_dialog)
        self.saveFileAction.triggered.connect(self.save_dialog)
        self.closeFileAction.triggered.connect(self.close_dialog)

        debugMenu = self.menuBar.addMenu('Debug')
        self.logAction = QAction(self.window.tr('Log'), self.window)
        self.logAction.setShortcut('Ctrl+D')
        self.logAction.setStatusTip('Open-Log')
        self.logAction.triggered.connect(self.control_log_window)
        debugMenu.addAction(self.logAction)

    def control_log_window(self):
        if self.window.log_window.isHidden():
            self.window.log_window.show()
            self.logAction.setText('Close-Log')
        else:
            self.window.log_window.hide()
            self.logAction.setText('Open-Log')

    def switch_tab(self, index):
        pass
        # if index == 2:
        #     self.menuBar.setHidden(False)
        # else:
        #     self.menuBar.setHidden(True)

    def _set_tab(self):
        self.tab_widget = QTabWidget()
        # self.tab_widget.currentChanged.connect(self.switch_tab)
        # tab_widget.setMaximumHeight(self.window.geometry().height() // 2)
        self.main_layout.addWidget(self.tab_widget)

        toolbox1 = QToolBox()
        toolbox2 = QToolBox()
        toolbox3 = QToolBox()
        toolbox4 = QToolBox()
        toolbox5 = QToolBox()

        groupbox1 = QGroupBox()
        groupbox2 = QGroupBox()
        groupbox3 = QGroupBox()
        groupbox4 = QGroupBox()
        groupbox5 = QGroupBox()

        toolbox1.addItem(groupbox1, "")
        toolbox2.addItem(groupbox2, "")
        toolbox3.addItem(groupbox3, "")
        toolbox4.addItem(groupbox4, "")
        toolbox5.addItem(groupbox5, "")

        self.tab_widget.addTab(toolbox1, "uArm")
        self.tab_widget.addTab(toolbox2, "xArm")
        self.tab_widget.addTab(toolbox3, "OpenMV")
        self.tab_widget.addTab(toolbox4, "Gcode")
        self.tab_widget.addTab(toolbox5, "WebView")

        uarm_layout = QVBoxLayout(groupbox1)
        xarm_layout = QVBoxLayout(groupbox2)
        openmv_layout = QHBoxLayout(groupbox3)
        gcode_layout = QVBoxLayout(groupbox4)
        webview_layout = QVBoxLayout(groupbox5)

        self.uarm_ui = UArmUI(self, uarm_layout)
        self.xarm_ui = XArmUI(self, xarm_layout)
        self.openmv_ui = OpenMV_UI(self, openmv_layout)
        self.gcode_ui = GcodeUI(self, gcode_layout)
        self.webview_ui = WebViewUI(self, webview_layout)
        self.tab_widget.setCurrentIndex(0)

    def new_dialog(self):
        self.openmv_ui.textEdit.setText('')
        self.openmv_ui.textEdit.filename = None
        self.openmv_ui.label_title.setText('untitled')
        self.tab_widget.setCurrentIndex(2)
        self.openmv_ui.textEdit.setDisabled(False)

    def open_dialog(self):
        fname = QFileDialog.getOpenFileName(self.window, 'Open file', '')
        if fname and fname[0]:
            with open(fname[0], "r") as f:
                self.openmv_ui.textEdit.setText(f.read())
                self.openmv_ui.label_title.setText(fname[0])
                self.openmv_ui.textEdit.filename = fname[0]
        self.tab_widget.setCurrentIndex(2)
        self.openmv_ui.textEdit.setDisabled(False)

    def save_dialog(self):
        widget = self.window.focusWidget()
        if widget:
            if not self.openmv_ui.textEdit.filename:
                fname = QFileDialog.getSaveFileName(self.window, 'Save File',
                                                    '')
                if fname and fname[0]:
                    self.openmv_ui.textEdit.filename = fname[0]
            if self.openmv_ui.textEdit.filename:
                data = widget.toPlainText()
                with open(self.openmv_ui.textEdit.filename, "w") as f:
                    f.write(data)

    def close_dialog(self):
        self.openmv_ui.textEdit.clear()
        self.openmv_ui.textEdit.filename = None
        self.openmv_ui.label_title.setText('')
        self.openmv_ui.textEdit.setDisabled(True)
예제 #13
0
class GUI(QtWidgets.QMainWindow):
    def __init__(self):
        '''Asetetaan muuttujille alkuarvoja ohjelman suorittamiseksi'''
        super().__init__()
        self.title = "Lujuusanalysaattori"
        self.left = 200
        self.top = 200
        self.width = 1300
        self.height = 700
        self.palkin_default_pituus = 5
        self.square_size = 10
        self.ikkuna()
        self.button_height = 75
        self.button_width = 150
        self.button_separation = 25
        self.x = 0
        self.y = 0
        self.palkin_leveys = 700
        self.palkin_korkeus = 75
        self.palkin_keskipiste = 650
        self.palkin_paatypiste = 1000
        self.yksikko_arvo = 0
        self.voima = 20
        self.maks_jannitys = "-"
        self.asteikko_teksti = QGraphicsSimpleTextItem()
        '''Lisää QGraphicsScenen ruudukon piirtämistä varten'''
        self.scene = QtWidgets.QGraphicsScene()
        self.scene.setSceneRect(0, -20, self.width - 200, self.height - 100)
        '''Suoritetaan lukuisia metodeja, jolla ohjelma "alustetaan"'''
        self.aloita_simulaatio()
        self.simulaatioikkuna()
        self.simulaatio_nappi()
        self.materiaali_valikko()
        self.uusi_palkki_nappi()
        self.lisaa_tuki_nappi()
        self.lisaa_ulkoinen_voima_nappi()
        self.poista_ulkoinen_voima_nappi()
        self.vaihda_tuki_nappi()

        Ominaisuudet.alkuarvot(self)
        self.lisaa_palkki()
        self.palkin_pituus_valikko()
        self.yksikko_pituus()
        self.asteikko()
        self.lisaa_asteikko_arvo()
        self.asteikko_teksti.hide()
        self.tulos_teksti()
        self.lisaa_seina_tuki()
        self.lisaa_tuki_alhaalta()
        self.ulkoinen_voima_valikko()
        self.ulkoinen_voima_nuoli_alatuki()
        self.ulkoinen_voima_nuoli_seinatuki()
        Ominaisuudet.alkuarvot(self)
        '''Asetetaan tietyille napeille tietty näkyvyys'''
        self.lisaa_tuki.setEnabled(False)
        self.simuloi.setEnabled(False)
        self.show()

    def ikkuna(self):
        '''Tekee ohjelman pääikkunan'''
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.setWindowTitle('Lujuusanalysaattori')
        self.horizontal = QtWidgets.QHBoxLayout()
        '''Luo menubarin'''
        self.uusiAction = QAction("Uusi simulaatio", self)
        self.uusiAction.setStatusTip("Luo uusi rakenne")
        self.uusiAction.triggered.connect(self.uusi_rakenne)
        self.uusiAction.setEnabled(True)
        self.uusiAction.setShortcut("Ctrl+N")

        self.tallennaAction = QAction("Tallenna simulaatio", self)
        self.tallennaAction.setStatusTip("Tallenna simulaatio")
        self.tallennaAction.triggered.connect(self.tallenna_rakenne)
        self.tallennaAction.setEnabled(False)
        self.tallennaAction.setShortcut("Ctrl+S")

        self.avaaAction = QAction("Lataa simulaatio", self)
        self.avaaAction.setStatusTip("Lataa simulaatio tiedostosta")
        self.avaaAction.triggered.connect(self.lataa_tallennettu_rakenne)
        self.avaaAction.setShortcut("Ctrl+O")

        self.exitAction = QAction("Exit", self)
        self.exitAction.setToolTip("Lopeta ohjelma")
        self.exitAction.triggered.connect(self.close_application)
        self.exitAction.setShortcut("Ctrl+E")
        self.statusBar()

        mainMenu = self.menuBar()
        fileMenu = mainMenu.addMenu('&File')
        aboutMenu = mainMenu.addMenu('&About')
        fileMenu.addAction(self.uusiAction)
        fileMenu.addAction(self.avaaAction)
        fileMenu.addAction(self.tallennaAction)
        fileMenu.addAction(self.exitAction)

    def tallenna_rakenne(self):
        '''Hoitaa rakenteen tallentamisen'''
        tallennus = Tallennin.tallenin(self)
        if tallennus == True:
            '''Kerrotaan käyttäjälle, että tallennus onnistui'''
            msgBox = QMessageBox()
            msgBox.setText("Tallennus onnistui!")
            msgBox.setWindowTitle("Onnistunut Tallennus")
            msgBox.setMinimumWidth(50)
            msgBox.addButton(QPushButton('OK'), QMessageBox.NoRole)
            msgBox.exec_()

    def lataa_tallennettu_rakenne(self):
        '''Metodi avaa QFileDialog ikkunan, josta käyttäjä valitsee tiedoston, jossa aiemmin tallennettu rakenne sijaitsee. Vain .txt -tiedostot 
        ovat ladattavissa '''
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        tiedosto, _ = QFileDialog.getOpenFileName(self,
                                                  "Valitse tiedosto",
                                                  "",
                                                  "txt Files (*.txt)",
                                                  options=options)
        lataus = Lataaja.lataaja(self, tiedosto)
        if lataus == False:
            return

        if lataus == True:
            self.uusi_rakenne()
            Lataaja.lataaja(self, tiedosto)
            tuen_tyyppi = Ominaisuudet.palauta_tuen_tyyppi(self)
            '''Jos tuki on seinästä, piirretään sitä vastaava grafiikka'''
            if tuen_tyyppi == 0:
                self.nayta_seina_tuki()
                self.gradient_seina_tuki()
            '''Jos tuki on alhaalta, piirretään sitä vastaava grafiikka'''
            if tuen_tyyppi == 1:
                self.nayta_tuki_alhaalta()
                self.gradient_alatuki()

            if tuen_tyyppi != 2:
                self.vaihda_tuki.show()
                self.lisaa_tuki.hide()
            '''Jos ulkoinen voima on asetettu, piirretään se'''
            ulkoinen_voima = int(
                Ominaisuudet.onko_ulkoinen_voima_asetettu(self))
            if ulkoinen_voima == 1:
                self.nayta_ulkoinen_voima()

            self.nayta_palkki()
            Laskin.laskin(self)
            self.paivita_tulos_teksti()
            self.tulos.show()
            self.sp.setValue(float(Ominaisuudet.palauta_palkin_pituus(self)))
            self.uusiAction.setEnabled(True)
            self.simuloi.setEnabled(True)
            '''Kerrotaan käyttäjälle, että kaikki onnistui'''
            msgBox = QMessageBox()
            msgBox.setText("Lataus onnistui!")
            msgBox.setWindowTitle("Onnistunut lataus")
            msgBox.addButton(QPushButton('OK'), QMessageBox.NoRole)
            msgBox.exec_()

    def aloita_simulaatio(self):
        '''Aloittaa simulaation'''
        self.setCentralWidget(QtWidgets.QWidget())
        self.horizontal = QtWidgets.QHBoxLayout()
        self.centralWidget().setLayout(self.horizontal)

    def simulaatioikkuna(self):
        '''lisää view näyttämistä varten'''
        self.view = QtWidgets.QGraphicsView(self.scene, self)
        self.view.adjustSize()
        self.view.show()
        self.horizontal.addWidget(self.view)

    def uusi_palkki_nappi(self):
        '''Luo Uusi palkki -napin'''
        self.uusi_palkki = QPushButton('Uusi palkki')
        self.uusi_palkki.setToolTip("Lisää uusi palkki")
        self.uusi_palkki.move(0, 0)
        self.uusi_palkki.resize(self.button_width, self.button_height)
        self.uusi_palkki.font = QtGui.QFont()
        self.uusi_palkki.font.setPointSize(12)
        self.uusi_palkki.setFont(self.uusi_palkki.font)
        self.uusi_palkki.setEnabled(True)
        self.scene.addWidget(self.uusi_palkki)
        self.uusi_palkki.clicked.connect(self.nayta_palkki)

    def nayta_palkki(self):
        '''Näyttää kaikki palkkiin liittyvät komponentit sekä asettaa uusi palkki -napin toimimattomaksi'''
        self.rect.show()
        self.palkin_pituus.show()
        self.sp.show()
        self.yksikko.show()
        self.asteikko_teksti.show()
        self.line.show()
        self.nuoli_1.show()
        self.nuoli_2.show()
        self.uusi_palkki.setEnabled(False)
        self.lisaa_tuki.setEnabled(True)
        self.materiaali_valinta.setEnabled(True)

    def lisaa_palkki(self):
        '''lisää palkin'''
        self.rect = QGraphicsRectItem(300, 200, self.palkin_leveys,
                                      self.palkin_korkeus)
        self.rect.setBrush(QBrush(4))
        self.scene.addItem(self.rect)
        self.rect.hide()
        self.lisaa_tuki.setEnabled(True)
        '''Aina kun on uusi palkki luotu, voidaan aloittaa simulaatio alusta'''
        self.uusiAction.setEnabled(True)

    def lisaa_tuki_nappi(self):
        '''Luo Lisää tuki -napin'''
        self.lisaa_tuki = QPushButton("Lisää tuki")
        self.lisaa_tuki.setToolTip("Lisää tuki")
        self.lisaa_tuki.move(0, self.button_height + self.button_separation)
        self.lisaa_tuki.resize(self.button_width, self.button_height)
        self.lisaa_tuki.font = QtGui.QFont()
        self.lisaa_tuki.font.setPointSize(12)
        self.lisaa_tuki.setFont(self.lisaa_tuki.font)
        self.lisaa_tuki.setEnabled(False)
        self.lisaa_tuki.clicked.connect(self.valitse_tuki)
        self.scene.addWidget(self.lisaa_tuki)

    def vaihda_tuki_nappi(self):
        '''Luo vaihda tuki -napin'''
        self.vaihda_tuki = QPushButton("Vaihda tuki")
        self.vaihda_tuki.setToolTip("Vaihda tuki")
        self.vaihda_tuki.move(0, self.button_height + self.button_separation)
        self.vaihda_tuki.resize(self.button_width, self.button_height)
        self.vaihda_tuki.setFont(self.lisaa_tuki.font)
        self.vaihda_tuki.clicked.connect(self.valitse_tuki)
        self.scene.addWidget(self.vaihda_tuki)
        self.vaihda_tuki.hide()

    def valitse_tuki(self):
        '''Tuen valinta.
        Jos tuki on seinästä (tyyppi = 0), kysytään halutaanko vaihtaa.
        Jos haluaa muutetaan tuen grafiikka ja arvo'''

        if Ominaisuudet.palauta_tuen_tyyppi(self) == 0:
            msgBox = QMessageBox()
            msgBox.setText("Haluatko vaihtaa tuen tyyppiä?")
            msgBox.addButton(QPushButton('En'), QMessageBox.NoRole)
            msgBox.addButton(QPushButton('Kyllä'), QMessageBox.YesRole)
            vastaus = msgBox.exec_()
            self.rect.setBrush(QBrush(4))

            if vastaus == 1:
                self.viiva_1.hide()
                self.viiva_2.hide()
                self.viiva_3.hide()
                self.viiva_4.hide()
                self.nayta_tuki_alhaalta()

                if int(Ominaisuudet.onko_ulkoinen_voima_asetettu(self)) == 1:
                    self.viiva.hide()
                    self.nuoli_3.hide()
                    self.viiva_5.show()
                    self.nuoli_6.show()

                Ominaisuudet.tuki(self, 1)
            return
        '''Jos tuki on alhaalta (tyyppi = 1), kysytään halutaanko vaihtaa.
        Jos haluaa muutetaan tuen grafiikka ja arvo'''
        if Ominaisuudet.palauta_tuen_tyyppi(self) == 1:
            msgBox = QMessageBox()
            msgBox.setText("Haluatko vaihtaa tuen tyyppiä?")
            msgBox.addButton(QPushButton('Kyllä'), QMessageBox.YesRole)
            msgBox.addButton(QPushButton('En'), QMessageBox.NoRole)
            vastaus = msgBox.exec_()
            self.rect.setBrush(QBrush(4))

            if vastaus == 0:
                Ominaisuudet.tuki(self, 0)
                self.nuoli_4.hide()
                self.nuoli_5.hide()
                self.nayta_seina_tuki()

                if int(Ominaisuudet.onko_ulkoinen_voima_asetettu(self)) == 1:
                    self.viiva.show()
                    self.nuoli_3.show()
                    self.viiva_5.hide()
                    self.nuoli_6.hide()

            if vastaus == 1:
                pass
        '''Jos tukea ei ole (tyyppi = 2). Tuen tyypin valinta'''
        if Ominaisuudet.palauta_tuen_tyyppi(self) == 2:
            msgBox = QMessageBox()
            msgBox.setText("Valitse tuen tyyppi")
            msgBox.addButton(QPushButton('Seinätuki'), QMessageBox.YesRole)
            msgBox.addButton(QPushButton('Tuki alhaalta'), QMessageBox.NoRole)
            vastaus = msgBox.exec_()
            self.vaihda_tuki.show()
            self.lisaa_tuki.hide()

            if vastaus == 0:
                self.nayta_seina_tuki()
                Ominaisuudet.tuki(self, 0)

            if vastaus == 1:
                self.nayta_tuki_alhaalta()
                Ominaisuudet.tuki(self, 1)
        '''Joka tapauksessa asetetaan ulkoisen voiman lisääminen mahdolliseksi
        sekä maalataan palkki normaaliksi'''
        self.lisaa_ulkoinen_voima.setEnabled(True)
        self.simuloi.setEnabled(True)

    def nayta_seina_tuki(self):
        '''Näytetään seinätukea kuvaavat grafiikat'''
        self.viiva_1.show()
        self.viiva_2.show()
        self.viiva_3.show()
        self.viiva_4.show()

    def nayta_tuki_alhaalta(self):
        '''Näytetään alatukea kuvaavat grafiikat'''
        self.nuoli_4.show()
        self.nuoli_5.show()

    def paivita_tuen_tyyppi(self, tyyppi):
        '''Päivittää tuen tyypin arvon Ominaisuudet luokassa'''
        Ominaisuudet.tuki(self, tyyppi)

    def lisaa_seina_tuki(self):
        '''Piirtää seinätukea kuvaavat viivat sekä asettaa self.tuen_tyyppi arvoksi 
        Asettaa SIMULOI-napin painettavaksi'''
        viiva = QtGui.QPen(QtCore.Qt.black, 2)
        viiva.setStyle(QtCore.Qt.SolidLine)
        self.viiva_1 = QGraphicsLineItem(QtCore.QLineF(300, 202, 275, 225))
        self.viiva_2 = QGraphicsLineItem(QtCore.QLineF(300, 222, 275, 245))
        self.viiva_3 = QGraphicsLineItem(QtCore.QLineF(300, 242, 275, 265))
        self.viiva_4 = QGraphicsLineItem(QtCore.QLineF(300, 262, 275, 285))

        self.scene.addItem(self.viiva_1)
        self.scene.addItem(self.viiva_2)
        self.scene.addItem(self.viiva_3)
        self.scene.addItem(self.viiva_4)
        self.viiva_1.hide()
        self.viiva_2.hide()
        self.viiva_3.hide()
        self.viiva_4.hide()
        tyyppi = 0
        Ominaisuudet.tuki(self, tyyppi)
        self.simuloi.setEnabled(True)

    def lisaa_tuki_alhaalta(self):
        '''Piirtää alhaalta tukemista kuvaavat grafiikat
        sekä asettaa self.tuen_tyyppi arvoksi 1'''
        leveys = 15  #nuolen leveus pikseleissä
        korkeus = 30  #nuuolen korkeus pikseleissä
        '''Nuolen kärkien koordinaatit'''
        nuoli_piste_1 = QtCore.QPointF(305, 275)
        nuoli_piste_2 = QtCore.QPointF(305 - leveys, 275 + korkeus)
        nuoli_piste_3 = QtCore.QPointF(305 + leveys, 275 + korkeus)

        nuoli_piste_4 = QtCore.QPointF(995, 275)
        nuoli_piste_5 = QtCore.QPointF(995 - leveys, 275 + korkeus)
        nuoli_piste_6 = QtCore.QPointF(995 + leveys, 275 + korkeus)
        '''Luodaan nuolia kuvaavat QPolygonF oliot'''
        self.nuoli_4 = QGraphicsPolygonItem(
            QtGui.QPolygonF([nuoli_piste_1, nuoli_piste_2, nuoli_piste_3]))
        self.nuoli_5 = QGraphicsPolygonItem(
            QtGui.QPolygonF([nuoli_piste_4, nuoli_piste_5, nuoli_piste_6]))
        self.nuoli_brush = QtGui.QBrush(1)
        self.nuoli_pencil = QtGui.QPen(QtCore.Qt.black, 2)
        self.nuoli_pencil.setStyle(QtCore.Qt.SolidLine)
        '''Lisätään nuolet sceneen'''
        self.scene.addItem(self.nuoli_4)
        self.scene.addItem(self.nuoli_5)
        self.nuoli_4.hide()
        self.nuoli_5.hide()

        tyyppi = 1
        Ominaisuudet.tuki(self, tyyppi)
        self.simuloi.setEnabled(True)

    def lisaa_ulkoinen_voima_nappi(self):
        '''Luo Lisää ulkoinen voima -napin'''
        self.lisaa_ulkoinen_voima = QPushButton("Lisää ulkoinen voima")
        self.lisaa_ulkoinen_voima.setToolTip("Lisää ulkoinen voima")
        self.lisaa_ulkoinen_voima.move(
            0, 2 * self.button_height + 2 * self.button_separation)
        self.lisaa_ulkoinen_voima.resize(self.button_width, self.button_height)
        self.lisaa_ulkoinen_voima.font = QtGui.QFont()
        self.lisaa_ulkoinen_voima.font.setPointSize(8)
        self.lisaa_ulkoinen_voima.setFont(self.lisaa_ulkoinen_voima.font)
        self.lisaa_ulkoinen_voima.clicked.connect(self.nayta_ulkoinen_voima)
        self.lisaa_ulkoinen_voima.clicked.connect(self.nollaa_gradientti)
        self.lisaa_ulkoinen_voima.setEnabled(False)
        self.scene.addWidget(self.lisaa_ulkoinen_voima)

    def poista_ulkoinen_voima_nappi(self):
        '''Luo poista ulkoinen voima -napin'''
        self.poista_ulkoinen_voima = QPushButton("Poista ulkoinen voima")
        self.poista_ulkoinen_voima.setToolTip("Poista ulkoinen voima")
        self.poista_ulkoinen_voima.move(
            0, 2 * self.button_height + 2 * self.button_separation)
        self.poista_ulkoinen_voima.resize(self.button_width,
                                          self.button_height)
        self.poista_ulkoinen_voima.setFont(self.lisaa_ulkoinen_voima.font)
        self.poista_ulkoinen_voima.clicked.connect(self.piilota_ulkoinen_voima)
        self.poista_ulkoinen_voima.clicked.connect(self.nollaa_gradientti)
        self.scene.addWidget(self.poista_ulkoinen_voima)
        self.poista_ulkoinen_voima.hide()

    def piilota_ulkoinen_voima(self):
        '''Piilotaa kaiken ulkoiseen voimaan liittyvän'''
        self.sp_voima.hide()
        self.yksikko_voima.hide()
        self.ulkoinen_voima.hide()
        self.lisaa_ulkoinen_voima.show()
        self.lisaa_ulkoinen_voima.setEnabled(True)
        self.viiva.hide()
        self.nuoli_3.hide()
        self.viiva_5.hide()
        self.nuoli_6.hide()
        self.poista_ulkoinen_voima.hide()
        self.lisaa_ulkoinen_voima.show()
        self.tulos.hide()

        Ominaisuudet.ulkoinen_voima(self, 0)

    def nayta_ulkoinen_voima(self):
        '''Näytetään ulkoinen voima riippuen tuen tyypistä'''
        self.sp_voima.show()
        self.yksikko_voima.show()
        self.ulkoinen_voima.show()
        self.lisaa_ulkoinen_voima.hide()
        self.poista_ulkoinen_voima.show()

        if int(Ominaisuudet.palauta_tuen_tyyppi(self)) == 0:
            self.viiva.show()
            self.nuoli_3.show()

        if int(Ominaisuudet.palauta_tuen_tyyppi(self)) == 1:
            self.viiva_5.show()
            self.nuoli_6.show()

        Ominaisuudet.ulkoinen_voima(self, 1)

    def ulkoinen_voima_valikko(self):
        '''Luo voiman suuruus -tekstin'''
        self.ulkoinen_voima = QGraphicsSimpleTextItem("Voiman suuruus")
        self.ulkoinen_voima.setPos(600, 5)
        self.ulkoinen_voima.font = QtGui.QFont()
        self.ulkoinen_voima.font.setPointSize(12)
        self.ulkoinen_voima.setFont(self.ulkoinen_voima.font)
        self.lisaa_ulkoinen_voima.setEnabled(False)
        self.scene.addItem(self.ulkoinen_voima)
        self.ulkoinen_voima.hide()
        '''Luo voiman arvon QSpinBoxin'''
        self.sp_voima = QSpinBox()
        self.sp_voima.move(750, 5)
        self.sp_voima.setRange(0, 10000)
        self.sp_voima.setSingleStep(1)
        self.sp_voima.setMinimumHeight(30)
        self.sp_voima.setValue(int(Ominaisuudet.palauta_voima(self)))
        self.sp_voima.valueChanged.connect(self.paivita_voima)
        self.scene.addWidget(self.sp_voima)
        self.sp_voima.hide()
        '''Luo yksikönvalinta QComboBOxin'''
        self.yksikko_voima = QComboBox()
        self.yksikko_voima.addItem("kN", 0)
        self.yksikko_voima.addItem("N", 1)
        self.yksikko_voima.move(820, 5)
        self.yksikko_voima.setMinimumHeight(30)
        self.yksikko_voima.setCurrentIndex(
            int(Ominaisuudet.palauta_voiman_yksikko(self)))
        self.yksikko_voima.setEditable(True)
        self.yksikko_voima.lineEdit().setAlignment(QtCore.Qt.AlignCenter)
        self.scene.addWidget(self.yksikko_voima)
        self.yksikko_voima.currentIndexChanged.connect(
            self.paivita_yksikko_voima)
        self.yksikko_voima.hide()

    def ulkoinen_voima_nuoli_seinatuki(self):
        '''Luo nuolen osoittamaan ulkoisen voiman paikkaa'''
        voima_viiva = QtGui.QPen(QtCore.Qt.black, 2)
        voima_viiva.setStyle(QtCore.Qt.SolidLine)
        '''Nuolen kärkien koordinaatit seinätuelle'''
        nuoli_piste_1 = QtCore.QPointF(self.palkin_paatypiste - 7, 185)
        nuoli_piste_2 = QtCore.QPointF(self.palkin_paatypiste, 200)
        nuoli_piste_3 = QtCore.QPointF(self.palkin_paatypiste + 7, 185)
        viiva_x = self.palkin_paatypiste
        self.viiva = QGraphicsLineItem(
            QtCore.QLineF(viiva_x, 100, viiva_x, 200))
        '''Luodaan nuoli QPolygonItem olio'''
        self.nuoli_3 = QGraphicsPolygonItem(
            QtGui.QPolygonF([nuoli_piste_1, nuoli_piste_2, nuoli_piste_3]))
        self.nuoli_brush = QtGui.QBrush(1)
        self.nuoli_pencil = QtGui.QPen(QtCore.Qt.black, 2)
        self.nuoli_pencil.setStyle(QtCore.Qt.SolidLine)
        '''Lisätään viiva sekä päiden nuolet sceneen'''
        self.scene.addItem(self.viiva)
        self.scene.addItem(self.nuoli_3)
        self.viiva.hide()
        self.nuoli_3.hide()
        '''Lisätään tieto, että voima on asetettu'''
        Ominaisuudet.ulkoinen_voima(self, 1)

    def ulkoinen_voima_nuoli_alatuki(self):
        '''Nuolen kärkien koordinaatit alhaalta tuetulle palkille'''
        nuoli_piste_1 = QtCore.QPointF(self.palkin_keskipiste - 7, 185)
        nuoli_piste_2 = QtCore.QPointF(self.palkin_keskipiste, 200)
        nuoli_piste_3 = QtCore.QPointF(self.palkin_keskipiste + 7, 185)
        viiva_x = self.palkin_keskipiste
        '''Luodaan nuoli QPolygonItem olio'''
        self.nuoli_6 = QGraphicsPolygonItem(
            QtGui.QPolygonF([nuoli_piste_1, nuoli_piste_2, nuoli_piste_3]))
        self.nuoli_brush = QtGui.QBrush(1)
        self.nuoli_pencil = QtGui.QPen(QtCore.Qt.black, 2)
        self.nuoli_pencil.setStyle(QtCore.Qt.SolidLine)

        self.viiva_5 = QGraphicsLineItem(
            QtCore.QLineF(viiva_x, 100, viiva_x, 200))
        '''Lisätään viiva sekä päiden nuolet sceneen'''
        self.scene.addItem(self.viiva_5)
        self.scene.addItem(self.nuoli_6)
        self.viiva_5.hide()
        self.nuoli_6.hide()
        '''Lisätään tieto, että voima on asetettu'''
        Ominaisuudet.ulkoinen_voima(self, 1)

    def paivita_voima(self):
        '''Lukee voiman arvon 
        ja kutsuu Ominaisuudet luoka metodia voima'''
        voima = self.sp_voima.value()
        Ominaisuudet.voima(self, voima)

    def paivita_yksikko_voima(self):
        '''Lukee ykiskön arvon 
        ja kutsuu Ominaisuudet-luokan metodia yksikko_voima'''
        self.yksikko_voima_arvo = self.yksikko_voima.currentData()
        Ominaisuudet.yksikko_voima(self, self.yksikko_voima_arvo)

    def materiaali_valikko(self):
        ''' Luo Materiaali-otsikon'''
        self.materiaali = QGraphicsSimpleTextItem("Materiaali")
        self.materiaali.setPos(
            0, 3 * self.button_height + 3 * self.button_separation)
        self.materiaali.font = QtGui.QFont()
        self.materiaali.font.setPointSize(12)
        self.materiaali.setFont(self.materiaali.font)
        self.scene.addItem(self.materiaali)
        '''Luo drop down valikon materiaalivalinnalle'''
        self.materiaali_valinta = QComboBox()
        self.materiaali_valinta.addItem("Teräs", 0)
        self.materiaali_valinta.addItem("Alumiini", 1)
        self.materiaali_valinta.addItem("Muovi", 2)
        self.materiaali_valinta.move(
            0, 3 * self.button_height + 3 * self.button_separation + 25)
        self.materiaali_valinta.resize(self.button_width,
                                       self.button_height - 25)
        self.materiaali_valinta.setEditable(True)
        self.materiaali_valinta.lineEdit().setAlignment(QtCore.Qt.AlignCenter)
        self.materiaali_valinta.setCurrentIndex(0)
        self.scene.addWidget(self.materiaali_valinta)
        self.materiaali_valinta.setEnabled(False)
        self.materiaali_valinta.currentIndexChanged.connect(
            self.paivita_materiaali)

    def paivita_materiaali(self):
        '''Lukee materiaalin arvon 
        ja kutsuu Ominaisuudet-luokan metodia materiaali'''
        materiaali = self.materiaali_valinta.currentData()
        Ominaisuudet.materiaali(self, materiaali)

    def simulaatio_nappi(self):
        '''Luo SIMULOI-napin'''
        self.simuloi = QPushButton('SIMULOI')
        self.simuloi.setToolTip('Simuloi valittu rakenne')
        self.simuloi.move(0,
                          4 * self.button_height + 4 * self.button_separation)
        self.simuloi.setStyleSheet("background-color:rgb(122, 201, 255)")
        self.simuloi.resize(self.button_width, self.button_height)
        self.simuloi.font = QtGui.QFont()
        self.simuloi.font.setPointSize(12)
        self.simuloi.setFont(self.simuloi.font)
        self.simuloi.setEnabled(False)
        self.simuloi.clicked.connect(self.simulaatio)
        self.scene.addWidget(self.simuloi)

    def simulaatio(self):
        '''Kutsuu laskentaa suorittavaa metodia ja tallentaa tuloksen.
        Tämän jälkeen kutsuu lopputuloksen esittävän tekstin päivittävää metodia
        sekä palkin visualisoivaa gradient-metodia'''
        Laskin.laskin(self)
        Ominaisuudet.palauta_tulos(self)
        self.paivita_tulos_teksti()
        self.tallennaAction.setEnabled(True)

        if Ominaisuudet.palauta_tuen_tyyppi(self) == 0:

            if Ominaisuudet.onko_ulkoinen_voima_asetettu(self) == 1:
                self.gradient_seina_tuki()

            if Ominaisuudet.onko_ulkoinen_voima_asetettu(self) == 0:
                self.gradient_seina_tuki_ei_voimaa()

        if Ominaisuudet.palauta_tuen_tyyppi(self) == 1:
            self.gradient_alatuki()

    def tulos_teksti(self):
        '''Lisää tekstin, joka kertoo maksimijänintyksen arvon'''
        teksti = "Maksimijännitys " + str(self.maks_jannitys) + " MPa"
        self.tulos = QGraphicsSimpleTextItem(teksti)
        self.tulos.setPos(550, 500)
        self.tulos.font = QtGui.QFont()
        self.tulos.font.setPointSize(12)
        self.tulos.setFont(self.tulos.font)
        self.scene.addItem(self.tulos)
        self.tulos.hide()

    def paivita_tulos_teksti(self):
        '''Päivittää maksimijännityksen arvoa kuvaavan tekstin'''
        maks_jannitys = Ominaisuudet.palauta_tulos(self)
        self.tulos.setText("Maksimijännitys " + str(maks_jannitys) + " MPa")
        self.tulos.show()

    def palkin_pituus_valikko(self):
        '''Luo palkin pituus tekstin sekä spinbox-valitsimen pituuden asettamista varten
        Päivittää palkin pituuden Ominaisuudet luokan avulla'''
        self.palkin_pituus = QGraphicsSimpleTextItem("Palkin pituus")
        self.palkin_pituus.setPos(300, 5)
        self.palkin_pituus.font = QtGui.QFont()
        self.palkin_pituus.font.setPointSize(12)
        self.palkin_pituus.setFont(self.palkin_pituus.font)
        self.scene.addItem(self.palkin_pituus)
        self.palkin_pituus.hide()

        self.sp = QSpinBox()
        self.scene.addWidget(self.sp)
        self.sp.hide()
        self.sp.move(450, 5)
        self.sp.setRange(0, 100)
        self.sp.setSingleStep(1)
        self.sp.setMinimumHeight(30)
        self.sp.setValue(int(Ominaisuudet.palauta_palkin_pituus(self)))
        self.paivita_pituus()
        self.sp.valueChanged.connect(self.paivita_pituus)

    def paivita_pituus(self):
        '''Lukee palkin pituuden ja aktivoi Ominaisuudet luokan meodin palkin pituus'''
        self.palkin_pituus_arvo = self.sp.value()
        Ominaisuudet.palkin_pituus(self, self.palkin_pituus_arvo)
        self.paivita_asteikon_arvot()

    def yksikko_pituus(self):
        '''Luo yksikönvalinta dropdown-menun
        ja arvon muuttuessa päivittää yksikön Ominaisuudet-luokassa'''
        self.yksikko = QComboBox()
        self.yksikko.addItem("m", 0)
        self.yksikko.addItem("cm", 1)
        self.yksikko.addItem("mm", 2)
        self.yksikko.move(500, 5)
        self.yksikko.setMinimumHeight(30)
        self.yksikko.setEditable(True)
        self.yksikko.lineEdit().setAlignment(QtCore.Qt.AlignCenter)
        self.yksikko.setCurrentIndex(
            Ominaisuudet.palauta_pituuden_yksikko(self))
        self.scene.addWidget(self.yksikko)
        self.yksikko.hide()
        self.yksikko_arvo = self.yksikko.currentData()
        self.yksikko.currentIndexChanged.connect(self.paivita_yksikko)

    def paivita_yksikko(self):
        '''Lukee yksikön arvon 
        ja kutsuu Ominaisuudet-luokan metodia yksikko'''
        self.yksikko_arvo = self.yksikko.currentData()
        Ominaisuudet.yksikko(self, self.yksikko_arvo)
        self.paivita_asteikon_arvot()

    def asteikko(self):
        ''''Luodaan viivaa kuvaava olio'''
        viiva = QtGui.QPen(QtCore.Qt.black, 2)
        viiva.setStyle(QtCore.Qt.SolidLine)
        '''Oikean puoleisen nuolen kärkien koordinaatit'''
        nuoli_1_piste_1 = QtCore.QPointF(990, 390)
        nuoli_1_piste_2 = QtCore.QPointF(1000, 400)
        nuoli_1_piste_3 = QtCore.QPointF(990, 410)
        '''Vasemman puoleisen nuolen kärkien koordinaatit'''
        nuoli_2_piste_1 = QtCore.QPointF(310, 390)
        nuoli_2_piste_2 = QtCore.QPointF(300, 400)
        nuoli_2_piste_3 = QtCore.QPointF(310, 410)
        '''Luodaan nuoli QPolygonF oliot'''
        self.nuoli_1 = QGraphicsPolygonItem(
            QtGui.QPolygonF(
                [nuoli_1_piste_1, nuoli_1_piste_2, nuoli_1_piste_3]))
        self.nuoli_2 = QGraphicsPolygonItem(
            QtGui.QPolygonF(
                [nuoli_2_piste_1, nuoli_2_piste_2, nuoli_2_piste_3]))

        self.nuoli_brush = QtGui.QBrush(1)
        self.nuoli_pencil = QtGui.QPen(QtCore.Qt.black, 2)
        self.nuoli_pencil.setStyle(QtCore.Qt.SolidLine)

        self.line = QGraphicsLineItem(QtCore.QLineF(300, 400, 1000, 400))
        '''Lisätään viiva sekä päiden nuolet sceneen'''
        self.scene.addItem(self.line)
        self.scene.addItem(self.nuoli_1)
        self.scene.addItem(self.nuoli_2)
        self.line.hide()
        self.nuoli_1.hide()
        self.nuoli_2.hide()

    def lisaa_asteikko_arvo(self):
        '''Lisää tekstikentän pituuden arvolle sekä yksikölle'''
        teksti = (str(Ominaisuudet.palauta_palkin_pituus(self)) + " " + "m")
        self.asteikko_teksti = QGraphicsSimpleTextItem()
        self.asteikko_teksti.setText(teksti)
        self.asteikko_teksti.setPos(650, 425)
        self.asteikko_teksti.font = QtGui.QFont()
        self.asteikko_teksti.font.setPointSize(12)
        self.asteikko_teksti.setFont(self.asteikko_teksti.font)
        self.scene.addItem(self.asteikko_teksti)
        self.asteikko_teksti.hide()

    def paivita_asteikon_arvot(self):
        '''Päivittää palkin pituutta kuvaavan asteikon'''
        yksikko = Ominaisuudet.palauta_pituuden_yksikko(self)

        if yksikko == 0:
            self.yksikko_merkki = "m"
        if yksikko == 1:
            self.yksikko_merkki = "cm"
        if yksikko == 2:
            self.yksikko_merkki = "mm"

        pituus = float(Ominaisuudet.palauta_palkin_pituus(self))
        teksti = str(str(pituus) + " " + self.yksikko_merkki)
        self.asteikko_teksti.setText(teksti)
        self.asteikko_teksti.show()

    def gradient_seina_tuki(self):
        '''Luo seinästä tuetun palkin rasitusta kuvaavan gradientin'''
        gradient = QLinearGradient(300, 200, 300 + self.palkin_leveys, 200)
        gradient.setColorAt(0, QColor(244, 72, 66))
        gradient.setColorAt(1, QColor(65, 244, 83))
        self.rect.setBrush(gradient)

    def gradient_seina_tuki_ei_voimaa(self):
        '''Luo ilman ulkoista voimaa olevan gradientin'''
        gradient = QLinearGradient(300, 200, 300 + (self.palkin_leveys / 2),
                                   200)
        gradient.setColorAt(0, QColor(244, 72, 66))
        gradient.setColorAt(1, QColor(65, 244, 83))
        self.rect.setBrush(gradient)

    def gradient_alatuki(self):
        '''Luo kahdella alatuella olevan palkin rasitusta kuvaavan gradientin'''
        gradient = QLinearGradient(300, 200, 300 + self.palkin_leveys, 200)
        gradient.setColorAt(0, QColor(65, 244, 83))
        gradient.setColorAt(0.5, QColor(244, 72, 66))
        gradient.setColorAt(1, QColor(65, 244, 83))
        self.rect.setBrush(gradient)

    def nollaa_gradientti(self):
        '''Asettaa palkin "normaaliksi"'''
        self.rect.setBrush(QBrush(4))

    def uusi_rakenne(self):
        '''Muokkaa ikkunaa uuden simulaation luomista varten'''
        self.rect.hide()
        self.ulkoinen_voima.hide()
        self.sp_voima.hide()
        self.yksikko_voima.hide()
        self.nuoli_1.hide()
        self.nuoli_2.hide()
        self.nuoli_3.hide()
        self.nuoli_4.hide()
        self.nuoli_5.hide()
        self.nuoli_6.hide()
        self.viiva_1.hide()
        self.viiva_2.hide()
        self.viiva_3.hide()
        self.viiva_4.hide()
        self.viiva_5.hide()
        self.viiva.hide()
        self.palkin_pituus.hide()
        self.sp.hide()
        self.yksikko.hide()
        self.line.hide()
        self.asteikko_teksti.hide()
        self.tulos.hide()
        self.nollaa_gradientti()
        self.lisaa_tuki.show()
        self.vaihda_tuki.hide()
        self.poista_ulkoinen_voima.hide()
        self.lisaa_ulkoinen_voima.show()
        Ominaisuudet.alkuarvot(self)
        '''Asettaa napit'''
        self.uusi_palkki.setEnabled(True)
        self.lisaa_ulkoinen_voima.setEnabled(False)
        self.lisaa_tuki.setEnabled(False)
        self.simuloi.setEnabled(False)
        self.tallennaAction.setEnabled(False)
        '''Päivittää tuen tyypiksi arvon, joka vastaa, ettei tukea ole'''
        self.tuen_tyyppi = 2

    def close_application(self):
        '''sulkee ohjelman'''
        sys.exit()