Example #1
0
class SimpleSearchControl(QtGui.QWidget):
    """A control that displays a single text field in which search keywords can
  be typed

  emits a search and a cancel signal if the user starts or cancels the search
  """

    expand_search_options_signal = QtCore.pyqtSignal()
    cancel_signal = QtCore.pyqtSignal()
    search_signal = QtCore.pyqtSignal(str)

    def __init__(self, parent):
        QtGui.QWidget.__init__(self, parent)
        layout = QtGui.QHBoxLayout()
        layout.setSpacing(0)
        layout.setMargin(3)

        # Search button
        self.search_button = QtGui.QToolButton()
        icon = Icon('tango/16x16/actions/system-search.png').getQIcon()
        self.search_button.setIcon(icon)
        self.search_button.setIconSize(QtCore.QSize(14, 14))
        self.search_button.setAutoRaise(True)
        self.search_button.setToolTip(_('Expand or collapse search options'))
        self.search_button.clicked.connect(self.emit_expand_search_options)

        # Search input
        from camelot.view.controls.decorated_line_edit import DecoratedLineEdit
        self.search_input = DecoratedLineEdit(self)
        self.search_input.set_background_text(_('Search...'))
        self.search_input.setToolTip(_('type words to search for'))
        #self.search_input.setStyleSheet('QLineEdit{ border-radius: 0.25em;}')
        self.search_input.returnPressed.connect(self.emit_search)
        self.search_input.textEdited.connect(self.emit_search)

        self.setFocusProxy(self.search_input)

        # Cancel button
        self.cancel_button = QtGui.QToolButton()
        icon = Icon('tango/16x16/actions/edit-clear.png').getQIcon()
        self.cancel_button.setIcon(icon)
        self.cancel_button.setIconSize(QtCore.QSize(14, 14))
        self.cancel_button.setAutoRaise(True)
        self.cancel_button.clicked.connect(self.emit_cancel)

        # Setup layout
        layout.addWidget(self.search_button)
        layout.addWidget(self.search_input)
        layout.addWidget(self.cancel_button)
        self.setLayout(layout)

    def search(self, search_text):
        """Start searching for search_text"""
        self.search_input.setText(search_text)
        self.emit_search()

    @QtCore.pyqtSlot()
    def emit_expand_search_options(self):
        self.expand_search_options_signal.emit()

    @QtCore.pyqtSlot()
    @QtCore.pyqtSlot(str)
    def emit_search(self, str=''):
        text = unicode(self.search_input.user_input())
        self.search_signal.emit(text)

    @QtCore.pyqtSlot()
    def emit_cancel(self):
        self.search_input.setText('')
        self.cancel_signal.emit()
Example #2
0
class DateEditor(CustomEditor):
    """Widget for editing date values"""

    calendar_action_trigger = QtCore.pyqtSignal()
    special_date_icon = Icon('tango/16x16/apps/office-calendar.png')
    
    def __init__(self, parent = None,
                       editable = True,
                       nullable = True, 
                       field_name = 'date',
                       **kwargs):
        CustomEditor.__init__(self, parent)

        self.setObjectName( field_name )
        self.date_format = local_date_format()
        self.line_edit = DecoratedLineEdit()
        self.line_edit.set_minimum_width( len( self.date_format ) )
        self.line_edit.set_background_text( QtCore.QDate(2000,1,1).toString(self.date_format) )

        # The order of creation of this widgets and their parenting
        # seems very sensitive under windows and creates system crashes
        # so don't change this without extensive testing on windows
        special_date_menu = QtGui.QMenu(self)
        calendar_widget_action = QtGui.QWidgetAction(special_date_menu)
        self.calendar_widget = QtGui.QCalendarWidget(special_date_menu)
        self.calendar_widget.activated.connect(self.calendar_widget_activated)
        self.calendar_widget.clicked.connect(self.calendar_widget_activated)
        calendar_widget_action.setDefaultWidget(self.calendar_widget)

        self.calendar_action_trigger.connect( special_date_menu.hide )
        special_date_menu.addAction(calendar_widget_action)
        special_date_menu.addAction(_('Today'))
        special_date_menu.addAction(_('Far future'))
        self.special_date = QtGui.QToolButton(self)
        self.special_date.setIcon( self.special_date_icon.getQIcon() )
        self.special_date.setAutoRaise(True)
        self.special_date.setToolTip(_('Calendar and special dates'))
        self.special_date.setMenu(special_date_menu)
        self.special_date.setPopupMode(QtGui.QToolButton.InstantPopup)
        self.special_date.setFixedHeight(self.get_height())
        self.special_date.setFocusPolicy(Qt.ClickFocus)
        # end of sensitive part

        if nullable:
            special_date_menu.addAction(_('Clear'))

        self.hlayout = QtGui.QHBoxLayout()
        self.hlayout.addWidget(self.line_edit)
        self.hlayout.addWidget(self.special_date)

        self.hlayout.setContentsMargins(0, 0, 0, 0)
        self.hlayout.setSpacing(0)
        self.hlayout.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        
        self.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self.hlayout)

        self.minimum = datetime.date.min
        self.maximum = datetime.date.max
        self.setFocusProxy(self.line_edit)

        self.line_edit.editingFinished.connect( self.line_edit_finished )
        self.line_edit.textEdited.connect(self.text_edited)
        special_date_menu.triggered.connect(self.set_special_date)

    def calendar_widget_activated(self, date):
        self.calendar_action_trigger.emit()
        self.set_value(date)
        self.editingFinished.emit()
        self.line_edit.setFocus()

    def line_edit_finished(self):
        self.setProperty( 'value', QtCore.QVariant( self.get_value() ) )
        self.valueChanged.emit()
        self.editingFinished.emit()

    def focusOutEvent(self, event):
        # explicitely set value on focus out to format the date in case
        # it was entered unformatted
        value = self.get_value()
        self.set_value( value )
        self.editingFinished.emit()

    def set_value(self, value):
        value = CustomEditor.set_value(self, value)
        self.setProperty( 'value', QtCore.QVariant( value ) )
        if value:
            qdate = QtCore.QDate(value)
            formatted_date = qdate.toString(self.date_format)
            self.line_edit.set_user_input(formatted_date)
            self.calendar_widget.setSelectedDate(qdate)
        else:
            self.line_edit.set_user_input('')
        self.valueChanged.emit()

    def text_edited(self, text ):
        try:
            date_from_string( self.line_edit.user_input() )
            self.line_edit.set_valid(True)
            self.valueChanged.emit()
        except ParsingError:
            self.line_edit.set_valid(False)

    def get_value(self):
        try:
            value = date_from_string( self.line_edit.user_input() )
        except ParsingError:
            value = None
        return CustomEditor.get_value(self) or value

    def set_field_attributes(self, editable = True,
                                   background_color = None,
                                   tooltip = None, **kwargs):
        self.set_enabled(editable)
        self.set_background_color(background_color)
        self.line_edit.setToolTip(unicode(tooltip or ''))

    def set_background_color(self, background_color):
        set_background_color_palette( self.line_edit, background_color )

    def set_enabled(self, editable=True):
        self.line_edit.setEnabled(editable)
        if editable:
            self.special_date.show()
        else:
            self.special_date.hide()

    def set_special_date(self, action):
        if action.text().compare(_('Today')) == 0:
            self.set_value(datetime.date.today())
        elif action.text().compare(_('Far future')) == 0:
            self.set_value(datetime.date( year = 2400, month = 12, day = 31 ))
        elif action.text().compare(_('Clear')) == 0:
            self.set_value(None)
        self.line_edit.setFocus()
        self.editingFinished.emit()
Example #3
0
class FileEditor(CustomEditor):
    """Widget for editing File fields"""

    document_pixmap = Icon('tango/16x16/mimetypes/x-office-document.png')

    def __init__(self,
                 parent=None,
                 storage=None,
                 field_name='file',
                 remove_original=False,
                 actions=[
                     field_action.DetachFile(),
                     field_action.OpenFile(),
                     field_action.UploadFile(),
                     field_action.SaveFile()
                 ],
                 **kwargs):
        CustomEditor.__init__(self, parent)
        self.setSizePolicy(QtGui.QSizePolicy.Preferred,
                           QtGui.QSizePolicy.Fixed)
        self.setObjectName(field_name)
        self.storage = storage
        self.filename = None  # the widget containing the filename
        self.value = None
        self.file_name = None
        self.remove_original = remove_original
        self.actions = actions
        self.setup_widget()

    def setup_widget(self):
        """Called inside init, overwrite this method for custom
        file edit widgets"""
        self.layout = QtWidgets.QHBoxLayout()
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)

        # Filename
        self.filename = DecoratedLineEdit(self)
        self.filename.set_minimum_width(20)
        self.filename.setFocusPolicy(Qt.ClickFocus)

        # Setup layout
        self.document_label = QtWidgets.QLabel(self)
        self.document_label.setPixmap(self.document_pixmap.getQPixmap())
        self.layout.addWidget(self.document_label)
        self.layout.addWidget(self.filename)
        self.add_actions(self.actions, self.layout)
        self.setLayout(self.layout)

    def file_completion_activated(self, index):
        from camelot.view.storage import create_stored_file
        source_index = index.model().mapToSource(index)
        if not self.completions_model.isDir(source_index):
            path = self.completions_model.filePath(source_index)
            create_stored_file(
                self,
                self.storage,
                self.stored_file_ready,
                filter=self.filter,
                remove_original=self.remove_original,
                filename=path,
            )

    def set_value(self, value):
        value = CustomEditor.set_value(self, value)
        self.value = value
        if value is not None:
            self.filename.setText(value.verbose_name)
        else:
            self.filename.setText('')
        self.update_actions()
        return value

    def get_value(self):
        return CustomEditor.get_value(self) or self.value

    def set_field_attributes(self, **kwargs):
        super(FileEditor, self).set_field_attributes(**kwargs)
        self.set_enabled(kwargs.get('editable', False))
        if self.filename:
            set_background_color_palette(self.filename,
                                         kwargs.get('background_color', None))
            self.filename.setToolTip(six.text_type(
                kwargs.get('tooltip') or ''))
        self.remove_original = kwargs.get('remove_original', False)

    def set_enabled(self, editable=True):
        self.filename.setEnabled(editable)
        self.filename.setReadOnly(not editable)
        self.document_label.setEnabled(editable)
        self.setAcceptDrops(editable)

    #
    # Drag & Drop
    #
    def dragEnterEvent(self, event):
        event.acceptProposedAction()

    def dragMoveEvent(self, event):
        event.acceptProposedAction()

    def dropEvent(self, event):
        from camelot.view.storage import create_stored_file
        if event.mimeData().hasUrls():
            url = event.mimeData().urls()[0]
            filename = url.toLocalFile()
            if filename:
                create_stored_file(
                    self,
                    self.storage,
                    self.stored_file_ready,
                    filter=self.filter,
                    remove_original=self.remove_original,
                    filename=filename,
                )
Example #4
0
class SimpleSearchControl(QtGui.QWidget):
    """A control that displays a single text field in which search keywords can
  be typed

  emits a search and a cancel signal if the user starts or cancels the search
  """

    expand_search_options_signal = QtCore.pyqtSignal()
    cancel_signal = QtCore.pyqtSignal()
    search_signal = QtCore.pyqtSignal(str)
    

    def __init__(self, parent):
        QtGui.QWidget.__init__(self, parent)
        layout = QtGui.QHBoxLayout()
        layout.setSpacing(0)
        layout.setMargin(3)

        # Search button
        self.search_button = QtGui.QToolButton()
        icon = Icon('tango/16x16/actions/system-search.png').getQIcon()
        self.search_button.setIcon(icon)
        self.search_button.setIconSize(QtCore.QSize(14, 14))
        self.search_button.setAutoRaise(True)
        self.search_button.setToolTip(_('Expand or collapse search options'))
        self.search_button.clicked.connect( self.emit_expand_search_options )

        # Search input
        from camelot.view.controls.decorated_line_edit import DecoratedLineEdit
        self.search_input = DecoratedLineEdit(self)
        self.search_input.set_background_text(_('Search...'))
        self.search_input.setToolTip(_('type words to search for'))
        #self.search_input.setStyleSheet('QLineEdit{ border-radius: 0.25em;}')
        self.search_input.returnPressed.connect( self.emit_search )
        self.search_input.textEdited.connect( self.emit_search )

        self.setFocusProxy( self.search_input )

        # Cancel button
        self.cancel_button = QtGui.QToolButton()
        icon = Icon('tango/16x16/actions/edit-clear.png').getQIcon()
        self.cancel_button.setIcon(icon)
        self.cancel_button.setIconSize(QtCore.QSize(14, 14))
        self.cancel_button.setAutoRaise(True)
        self.cancel_button.clicked.connect( self.emit_cancel )

        # Setup layout
        layout.addWidget(self.search_button)
        layout.addWidget(self.search_input)
        layout.addWidget(self.cancel_button)
        self.setLayout(layout)

    def search(self, search_text):
        """Start searching for search_text"""
        self.search_input.setText(search_text)
        self.emit_search()

    @QtCore.pyqtSlot()
    def emit_expand_search_options(self):
        self.expand_search_options_signal.emit()

    @QtCore.pyqtSlot()
    @QtCore.pyqtSlot(str)
    def emit_search(self, str=''):
        text = unicode(self.search_input.user_input())
        self.search_signal.emit( text )

    @QtCore.pyqtSlot()
    def emit_cancel(self):
        self.search_input.setText('')
        self.cancel_signal.emit()
Example #5
0
class DateEditor(CustomEditor):
    """Widget for editing date values"""

    calendar_action_trigger = QtCore.pyqtSignal()
    special_date_icon = Icon('tango/16x16/apps/office-calendar.png')

    def __init__(self,
                 parent=None,
                 editable=True,
                 nullable=True,
                 field_name='date',
                 **kwargs):
        CustomEditor.__init__(self, parent)

        self.setObjectName(field_name)
        self.date_format = local_date_format()
        self.line_edit = DecoratedLineEdit()
        self.line_edit.set_minimum_width(
            unicode(QtCore.QDate(2000, 12, 22).toString(self.date_format)))
        self.line_edit.set_background_text(
            QtCore.QDate(2000, 1, 1).toString(self.date_format))

        # The order of creation of this widgets and their parenting
        # seems very sensitive under windows and creates system crashes
        # so don't change this without extensive testing on windows
        special_date_menu = QtGui.QMenu(self)
        calendar_widget_action = QtGui.QWidgetAction(special_date_menu)
        self.calendar_widget = QtGui.QCalendarWidget(special_date_menu)
        self.calendar_widget.activated.connect(self.calendar_widget_activated)
        self.calendar_widget.clicked.connect(self.calendar_widget_activated)
        calendar_widget_action.setDefaultWidget(self.calendar_widget)

        self.calendar_action_trigger.connect(special_date_menu.hide)
        special_date_menu.addAction(calendar_widget_action)
        special_date_menu.addAction(_('Today'))
        special_date_menu.addAction(_('Far future'))
        self.special_date = QtGui.QToolButton(self)
        self.special_date.setIcon(self.special_date_icon.getQIcon())
        self.special_date.setAutoRaise(True)
        self.special_date.setToolTip(_('Calendar and special dates'))
        self.special_date.setMenu(special_date_menu)
        self.special_date.setPopupMode(QtGui.QToolButton.InstantPopup)
        self.special_date.setFixedHeight(self.get_height())
        self.special_date.setFocusPolicy(Qt.ClickFocus)
        # end of sensitive part

        if nullable:
            special_date_menu.addAction(_('Clear'))

        self.hlayout = QtGui.QHBoxLayout()
        self.hlayout.addWidget(self.line_edit)
        self.hlayout.addWidget(self.special_date)

        self.hlayout.setContentsMargins(0, 0, 0, 0)
        self.hlayout.setSpacing(0)
        self.hlayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter)

        self.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self.hlayout)

        self.minimum = datetime.date.min
        self.maximum = datetime.date.max
        self.setFocusProxy(self.line_edit)

        self.line_edit.editingFinished.connect(self.line_edit_finished)
        self.line_edit.textEdited.connect(self.text_edited)
        special_date_menu.triggered.connect(self.set_special_date)

    def calendar_widget_activated(self, date):
        self.calendar_action_trigger.emit()
        self.set_value(date)
        self.editingFinished.emit()
        self.line_edit.setFocus()

    def line_edit_finished(self):
        self.setProperty('value', QtCore.QVariant(self.get_value()))
        self.valueChanged.emit()
        self.editingFinished.emit()

    def focusOutEvent(self, event):
        # explicitely set value on focus out to format the date in case
        # it was entered unformatted
        value = self.get_value()
        self.set_value(value)
        self.editingFinished.emit()

    def set_value(self, value):
        value = CustomEditor.set_value(self, value)
        self.setProperty('value', QtCore.QVariant(value))
        if value:
            qdate = QtCore.QDate(value)
            formatted_date = qdate.toString(self.date_format)
            self.line_edit.set_user_input(formatted_date)
            self.calendar_widget.setSelectedDate(qdate)
        else:
            self.line_edit.set_user_input('')
        self.valueChanged.emit()

    def text_edited(self, text):
        try:
            date_from_string(self.line_edit.user_input())
            self.line_edit.set_valid(True)
            self.valueChanged.emit()
        except ParsingError:
            self.line_edit.set_valid(False)

    def get_value(self):
        try:
            value = date_from_string(self.line_edit.user_input())
        except ParsingError:
            value = None
        return CustomEditor.get_value(self) or value

    def set_field_attributes(self,
                             editable=True,
                             background_color=None,
                             tooltip=None,
                             **kwargs):
        self.set_enabled(editable)
        self.set_background_color(background_color)
        self.line_edit.setToolTip(unicode(tooltip or ''))

    def set_background_color(self, background_color):
        set_background_color_palette(self.line_edit, background_color)

    def set_enabled(self, editable=True):
        self.line_edit.setEnabled(editable)
        if editable:
            self.special_date.show()
        else:
            self.special_date.hide()

    def set_special_date(self, action):
        if action.text().compare(_('Today')) == 0:
            self.set_value(datetime.date.today())
        elif action.text().compare(_('Far future')) == 0:
            self.set_value(datetime.date(year=2400, month=12, day=31))
        elif action.text().compare(_('Clear')) == 0:
            self.set_value(None)
        self.line_edit.setFocus()
        self.editingFinished.emit()
Example #6
0
class LocalFileEditor(CustomEditor):
    """Widget for browsing local files and directories"""

    browse_icon = Icon('tango/16x16/places/folder-saved-search.png')

    def __init__(self,
                 parent=None,
                 field_name='local_file',
                 directory=False,
                 save_as=False,
                 file_filter='All files (*)',
                 **kwargs):
        CustomEditor.__init__(self, parent)
        self.setObjectName(field_name)
        self._directory = directory
        self._save_as = save_as
        self._file_filter = file_filter
        self.setup_widget()

    def setup_widget(self):
        """Called inside init, overwrite this method for custom
        file edit widgets"""
        layout = QtGui.QHBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)

        browse_button = QtGui.QToolButton(self)
        browse_button.setFocusPolicy(Qt.ClickFocus)
        browse_button.setIcon(self.browse_icon.getQIcon())
        browse_button.setToolTip(_('Browse'))
        browse_button.setAutoRaise(True)
        browse_button.clicked.connect(self.browse_button_clicked)

        self.filename = DecoratedLineEdit(self)
        self.filename.editingFinished.connect(self.filename_editing_finished)
        self.setFocusProxy(self.filename)

        layout.addWidget(self.filename)
        layout.addWidget(browse_button)
        self.setLayout(layout)

    @QtCore.pyqtSlot()
    def filename_editing_finished(self):
        self.valueChanged.emit()
        self.editingFinished.emit()

    @QtCore.pyqtSlot()
    def browse_button_clicked(self):
        current_directory = os.path.dirname(self.get_value())
        if self._directory:
            value = QtGui.QFileDialog.getExistingDirectory(
                self, directory=current_directory)
        elif self._save_as:
            value = QtGui.QFileDialog.getSaveFileName(
                self, filter=self._file_filter, directory=current_directory)
        else:
            value = QtGui.QFileDialog.getOpenFileName(
                self, filter=self._file_filter, directory=current_directory)
        value = os.path.abspath(unicode(value))
        self.filename.setText(value)
        self.valueChanged.emit()
        self.editingFinished.emit()

    def set_value(self, value):
        value = CustomEditor.set_value(self, value)
        if value:
            self.filename.setText(value)
        else:
            self.filename.setText('')
        self.valueChanged.emit()
        return value

    def get_value(self):
        return CustomEditor.get_value(self) or unicode(self.filename.text())

    value = QtCore.pyqtProperty(str, get_value, set_value)

    def set_field_attributes(self,
                             editable=True,
                             background_color=None,
                             tooltip=None,
                             **kwargs):
        self.setEnabled(editable)
        if self.filename:
            set_background_color_palette(self.filename, background_color)
            self.filename.setToolTip(unicode(tooltip or ''))
Example #7
0
class Many2OneEditor(CustomEditor):
    """Widget for editing many 2 one relations"""

    arrow_down_key_pressed = QtCore.qt_signal()

    class CompletionsModel(QtCore.QAbstractListModel):
        def __init__(self, parent=None):
            QtCore.QAbstractListModel.__init__(self, parent)
            self._completions = []

        def setCompletions(self, completions):
            self._completions = completions
            self.layoutChanged.emit()

        def data(self, index, role):
            return py_to_variant(self._completions[index.row()].get(role))

        def rowCount(self, index=None):
            return len(self._completions)

        def columnCount(self, index=None):
            return 1

    def __init__(self,
                 admin=None,
                 parent=None,
                 editable=True,
                 field_name='manytoone',
                 actions=[
                     field_action.ClearObject(),
                     field_action.SelectObject(),
                     field_action.NewObject(),
                     field_action.OpenObject()
                 ],
                 **kwargs):
        """
        :param entity_admin : The Admin interface for the object on the one
        side of the relation
        """
        CustomEditor.__init__(self, parent)
        self.setSizePolicy(QtGui.QSizePolicy.Preferred,
                           QtGui.QSizePolicy.Fixed)
        self.setObjectName(field_name)
        self.admin = admin
        self.new_value = None
        self._entity_representation = ''
        self.obj = None
        self._last_highlighted_entity_getter = None

        self.layout = QtWidgets.QHBoxLayout()
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)

        # Search input
        self.search_input = DecoratedLineEdit(self)
        self.search_input.setPlaceholderText(_('Search...'))
        self.search_input.textEdited.connect(self.textEdited)
        self.search_input.set_minimum_width(20)
        self.search_input.arrow_down_key_pressed.connect(
            self.on_arrow_down_key_pressed)
        # suppose garbage was entered, we need to refresh the content
        self.search_input.editingFinished.connect(
            self.search_input_editing_finished)
        self.setFocusProxy(self.search_input)

        # Search Completer
        self.completer = QtGui.QCompleter()
        self.completions_model = self.CompletionsModel(self.completer)
        self.completer.setModel(self.completions_model)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setCompletionMode(
            QtGui.QCompleter.UnfilteredPopupCompletion)
        self.completer.activated[QtCore.QModelIndex].connect(
            self.completionActivated)
        self.completer.highlighted[QtCore.QModelIndex].connect(
            self.completion_highlighted)
        self.search_input.setCompleter(self.completer)

        # Setup layout
        self.layout.addWidget(self.search_input)
        self.setLayout(self.layout)
        self.add_actions(actions, self.layout)
        get_signal_handler().connect_signals(self)

    def set_field_attributes(self, **kwargs):
        super(Many2OneEditor, self).set_field_attributes(**kwargs)
        set_background_color_palette(self.search_input,
                                     kwargs.get('background_color'))
        self.search_input.setToolTip(kwargs.get('tooltip') or '')
        self.search_input.setEnabled(kwargs.get('editable', False))
        self.update_actions()

    def on_arrow_down_key_pressed(self):
        self.arrow_down_key_pressed.emit()

    def textEdited(self, text):
        self._last_highlighted_entity_getter = None
        text = six.text_type(self.search_input.text())

        def create_search_completion(text):
            return lambda: self.search_completions(text)

        post(create_search_completion(six.text_type(text)),
             self.display_search_completions)
        self.completer.complete()

    def search_completions(self, text):
        """Search for object that match text, to fill the list of completions

        :return: a list of tuples of (dict_of_object_representation, object)
        """
        search_decorator = create_entity_search_query_decorator(
            self.admin, text)
        if search_decorator:
            sresult = [
                self.admin.get_search_identifiers(e)
                for e in search_decorator(self.admin.get_query()).limit(20)
            ]
            return text, sresult
        return text, []

    def display_search_completions(self, prefix_and_completions):
        assert object_thread(self)
        prefix, completions = prefix_and_completions
        self.completions_model.setCompletions(completions)
        self.completer.setCompletionPrefix(prefix)
        self.completer.complete()

    def completionActivated(self, index):
        obj = index.data(Qt.EditRole)
        self.set_object(variant_to_py(obj))

    def completion_highlighted(self, index):
        obj = index.data(Qt.EditRole)
        self._last_highlighted_entity_getter = variant_to_py(obj)

    @QtCore.qt_slot(object, object)
    def handle_entity_update(self, sender, entity):
        if entity is self.get_value():
            self.set_object(entity, False)

    @QtCore.qt_slot(object, object)
    def handle_entity_delete(self, sender, entity):
        if entity is self.get_value():
            self.set_object(None, False)

    @QtCore.qt_slot(object, object)
    def handle_entity_create(self, sender, entity):
        if entity is self.new_value:
            self.new_value = None
            self.set_object(entity)

    def search_input_editing_finished(self):
        if self.obj is None:
            # Only try to 'guess' what the user meant when no entity is set
            # to avoid inappropriate removal of data, (eg when the user presses
            # Esc, editingfinished will be called as well, and we should not
            # overwrite the current entity set)
            if self._last_highlighted_entity_getter:
                self.set_object(self._last_highlighted_entity_getter)
            elif self.completions_model.rowCount() == 1:
                # There is only one possible option
                index = self.completions_model.index(0, 0)
                entity_getter = variant_to_py(index.data(Qt.EditRole))
                self.set_object(entity_getter)
        self.search_input.setText(self._entity_representation or u'')

    def set_value(self, value):
        """:param value: either ValueLoading, or a function that returns None
        or the entity to be shown in the editor"""
        self._last_highlighted_entity_getter = None
        self.new_value = None
        value = CustomEditor.set_value(self, value)
        self.set_object(value, propagate=False)
        self.update_actions()

    def get_value(self):
        """:return: a function that returns the selected entity or ValueLoading
        or None"""
        value = CustomEditor.get_value(self)
        if value is not None:
            return value
        return self.obj

    @QtCore.qt_slot(tuple)
    def set_instance_representation(self, representation_and_propagate):
        """Update the gui"""
        (desc, propagate) = representation_and_propagate
        self._entity_representation = desc
        self.search_input.setText(desc or u'')

        if propagate:
            self.editingFinished.emit()

    def set_object(self, obj, propagate=True):
        self.obj = obj

        def get_instance_representation(obj, propagate):
            """Get a representation of the instance"""
            if obj is not None:
                return (self.admin.get_verbose_object_name(obj), propagate)
            return (None, propagate)

        post(
            update_wrapper(
                partial(get_instance_representation, obj, propagate),
                get_instance_representation), self.set_instance_representation)

    selected_object = property(fset=set_object)
Example #8
0
class LocalFileEditor( CustomEditor ):
    """Widget for browsing local files and directories"""

    browse_icon =  Icon( 'tango/16x16/places/folder-saved-search.png' )

    def __init__(self, 
                 parent = None, 
                 field_name = 'local_file', 
                 directory = False, 
                 save_as = False,
                 file_filter = 'All files (*)',
                 **kwargs):
        CustomEditor.__init__(self, parent)
        self.setObjectName( field_name )
        self._directory = directory
        self._save_as = save_as
        self._file_filter = file_filter
        self.setup_widget()

    def setup_widget(self):
        """Called inside init, overwrite this method for custom
        file edit widgets"""
        layout = QtGui.QHBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)

        browse_button = QtGui.QToolButton( self )
        browse_button.setFocusPolicy( Qt.ClickFocus )
        browse_button.setIcon( self.browse_icon.getQIcon() )
        browse_button.setToolTip( _('Browse') )
        browse_button.setAutoRaise( True )
        browse_button.clicked.connect( self.browse_button_clicked )

        self.filename = DecoratedLineEdit(self)
        self.filename.editingFinished.connect( self.filename_editing_finished )
        self.setFocusProxy( self.filename )
        
        layout.addWidget( self.filename )
        layout.addWidget( browse_button )
        self.setLayout( layout )

    @QtCore.pyqtSlot()
    def filename_editing_finished(self):
        self.valueChanged.emit()
        self.editingFinished.emit()

    @QtCore.pyqtSlot()
    def browse_button_clicked(self):
        current_directory = os.path.dirname( self.get_value() )
        if self._directory:
            value = QtGui.QFileDialog.getExistingDirectory( self,
                                                            directory = current_directory )
        elif self._save_as:
            value = QtGui.QFileDialog.getSaveFileName( self,
                                                       filter = self._file_filter,
                                                       directory = current_directory )
        else:
            value = QtGui.QFileDialog.getOpenFileName( self,
                                                       filter = self._file_filter,
                                                       directory = current_directory )
        value = os.path.abspath( unicode( value ) )
        self.filename.setText( value )
        self.valueChanged.emit()
        self.editingFinished.emit()

    def set_value(self, value):
        value = CustomEditor.set_value(self, value)
        if value:
            self.filename.setText( value )
        else:
            self.filename.setText( '' )
        self.valueChanged.emit()
        return value

    def get_value(self):
        return CustomEditor.get_value(self) or unicode( self.filename.text() )
    
    value = QtCore.pyqtProperty( str, get_value, set_value )

    def set_field_attributes( self, 
                              editable = True,
                              background_color = None,
                              tooltip = None,
                              **kwargs):
        self.setEnabled( editable )
        if self.filename:
            set_background_color_palette( self.filename, background_color )
            self.filename.setToolTip(unicode(tooltip or ''))
Example #9
0
class FileEditor(CustomEditor):
    """Widget for editing File fields"""

    filter = "All files (*)"
    add_icon = Icon("tango/16x16/actions/list-add.png")
    open_icon = Icon("tango/16x16/actions/document-open.png")
    clear_icon = Icon("tango/16x16/actions/edit-delete.png")
    save_as_icon = Icon("tango/16x16/actions/document-save-as.png")
    document_pixmap = Icon("tango/16x16/mimetypes/x-office-document.png")

    def __init__(self, parent=None, storage=None, field_name="file", remove_original=False, **kwargs):
        CustomEditor.__init__(self, parent)
        self.setObjectName(field_name)
        self.storage = storage
        self.filename = None  # the widget containing the filename
        self.value = None
        self.remove_original = remove_original
        self.setup_widget()

    def setup_widget(self):
        """Called inside init, overwrite this method for custom
        file edit widgets"""
        self.layout = QtGui.QHBoxLayout()
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)

        # Save As button
        self.save_as_button = QtGui.QToolButton()
        self.save_as_button.setFocusPolicy(Qt.ClickFocus)
        self.save_as_button.setIcon(self.save_as_icon.getQIcon())
        self.save_as_button.setToolTip(_("Save file as"))
        self.save_as_button.setAutoRaise(True)
        self.save_as_button.clicked.connect(self.save_as_button_clicked)

        # Clear button
        self.clear_button = QtGui.QToolButton()
        self.clear_button.setFocusPolicy(Qt.ClickFocus)
        self.clear_button.setIcon(self.clear_icon.getQIcon())
        self.clear_button.setToolTip(_("Delete file"))
        self.clear_button.setAutoRaise(True)
        self.clear_button.clicked.connect(self.clear_button_clicked)

        # Open button
        self.open_button = QtGui.QToolButton()
        self.open_button.setFocusPolicy(Qt.ClickFocus)
        self.open_button.setIcon(self.open_icon.getQIcon())
        self.open_button.setToolTip(_("Open file"))
        self.open_button.clicked.connect(self.open_button_clicked)
        self.open_button.setAutoRaise(True)

        # Add button
        self.add_button = QtGui.QToolButton()
        self.add_button.setFocusPolicy(Qt.StrongFocus)
        self.add_button.setIcon(self.add_icon.getQIcon())
        self.add_button.setToolTip(_("Attach file"))
        self.add_button.clicked.connect(self.add_button_clicked)
        self.add_button.setAutoRaise(True)

        # Filename
        self.filename = DecoratedLineEdit(self)
        self.filename.set_minimum_width(20)
        self.filename.setFocusPolicy(Qt.ClickFocus)

        # Search Completer
        #
        # Turn completion off, since it creates a thread per field on a form
        #
        # self.completer = QtGui.QCompleter()
        # self.completions_model = QtGui.QFileSystemModel()
        # self.completer.setCompletionMode(
        #    QtGui.QCompleter.UnfilteredPopupCompletion
        # )
        # self.completer.setModel( self.completions_model )
        # self.completer.activated[QtCore.QModelIndex].connect(self.file_completion_activated)
        # self.filename.setCompleter( self.completer )
        # settings = QtCore.QSettings()
        # last_path = settings.value('lastpath').toString()

        # # This setting of a rootPath causes a major delay on Windows, since
        # # the QFileSystemModel starts to fetch file information in a non-
        # # blocking way (although the documentation state the opposite).
        # # On Linux, there is no such delay, so it's safe to set such a root
        # # path and let the underlaying system start indexing.
        # import sys
        # if sys.platform != "win32":
        #    self.completions_model.setRootPath( last_path )

        # Setup layout
        self.document_label = QtGui.QLabel(self)
        self.document_label.setPixmap(self.document_pixmap.getQPixmap())
        self.layout.addWidget(self.document_label)
        self.layout.addWidget(self.filename)
        self.layout.addWidget(self.clear_button)
        self.layout.addWidget(self.open_button)
        self.layout.addWidget(self.add_button)
        self.layout.addWidget(self.save_as_button)
        self.setLayout(self.layout)

    def file_completion_activated(self, index):
        from camelot.view.storage import create_stored_file

        source_index = index.model().mapToSource(index)
        if not self.completions_model.isDir(source_index):
            path = self.completions_model.filePath(source_index)
            create_stored_file(
                self,
                self.storage,
                self.stored_file_ready,
                filter=self.filter,
                remove_original=self.remove_original,
                filename=path,
            )

    def set_value(self, value):
        value = CustomEditor.set_value(self, value)
        self.value = value
        if value:
            self.clear_button.setVisible(True)
            self.save_as_button.setVisible(True)
            self.open_button.setVisible(True)
            self.add_button.setVisible(False)
            self.filename.setText(value.verbose_name)
        else:
            self.clear_button.setVisible(False)
            self.save_as_button.setVisible(False)
            self.open_button.setVisible(False)
            self.add_button.setVisible(True)
            self.filename.setText("")
        return value

    def get_value(self):
        return CustomEditor.get_value(self) or self.value

    def set_field_attributes(self, editable=True, background_color=None, tooltip=None, remove_original=False, **kwargs):
        self.set_enabled(editable)
        if self.filename:
            set_background_color_palette(self.filename, background_color)
            self.filename.setToolTip(unicode(tooltip or ""))
        self.remove_original = remove_original

    def set_enabled(self, editable=True):
        self.clear_button.setEnabled(editable)
        self.add_button.setEnabled(editable)
        self.filename.setEnabled(editable)
        self.filename.setReadOnly(not editable)
        self.document_label.setEnabled(editable)
        self.setAcceptDrops(editable)

    def stored_file_ready(self, stored_file):
        """Slot to be called when a new stored_file has been created by
        the storage"""
        self.set_value(stored_file)
        self.editingFinished.emit()

    def save_as_button_clicked(self):
        from camelot.view.storage import save_stored_file

        value = self.get_value()
        if value:
            save_stored_file(self, value)

    def add_button_clicked(self):
        from camelot.view.storage import create_stored_file

        create_stored_file(
            self, self.storage, self.stored_file_ready, filter=self.filter, remove_original=self.remove_original
        )

    def open_button_clicked(self):
        from camelot.view.storage import open_stored_file

        open_stored_file(self, self.value)

    def clear_button_clicked(self):
        answer = QtGui.QMessageBox.question(
            self,
            _("Remove this file ?"),
            _("If you continue, you will no longer be able to open this file."),
            QtGui.QMessageBox.Yes,
            QtGui.QMessageBox.No,
        )
        if answer == QtGui.QMessageBox.Yes:
            self.value = None
            self.editingFinished.emit()

    #
    # Drag & Drop
    #
    def dragEnterEvent(self, event):
        event.acceptProposedAction()

    def dragMoveEvent(self, event):
        event.acceptProposedAction()

    def dropEvent(self, event):
        from camelot.view.storage import create_stored_file

        if event.mimeData().hasUrls():
            url = event.mimeData().urls()[0]
            filename = url.toLocalFile()
            if filename:
                create_stored_file(
                    self,
                    self.storage,
                    self.stored_file_ready,
                    filter=self.filter,
                    remove_original=self.remove_original,
                    filename=filename,
                )
Example #10
0
class Many2OneEditor( CustomEditor ):
    """Widget for editing many 2 one relations"""

    new_icon = Icon('tango/16x16/actions/document-new.png')
    search_icon = Icon('tango/16x16/actions/system-search.png')

    arrow_down_key_pressed = QtCore.pyqtSignal()

    class CompletionsModel(QtCore.QAbstractListModel):

        def __init__(self, parent=None):
            QtCore.QAbstractListModel.__init__(self, parent)
            self._completions = []

        def setCompletions(self, completions):
            self._completions = completions
            self.layoutChanged.emit()

        def data(self, index, role):
            if role == Qt.DisplayRole:
                return QtCore.QVariant(self._completions[index.row()][0])
            elif role == Qt.EditRole:
                return QtCore.QVariant(self._completions[index.row()][1])
            return QtCore.QVariant()

        def rowCount(self, index=None):
            return len(self._completions)

        def columnCount(self, index=None):
            return 1

    def __init__(self, 
                 admin=None, 
                 parent=None, 
                 editable=True, 
                 field_name='manytoone', 
                 **kwargs):
        """:param entity_admin : The Admin interface for the object on the one
        side of the relation
        """

        CustomEditor.__init__(self, parent)
        self.setObjectName( field_name )
        self.admin = admin
        self.entity_set = False
        self._editable = editable
        self._entity_representation = ''
        self.entity_instance_getter = None
        self._last_highlighted_entity_getter = None

        self.layout = QtGui.QHBoxLayout()
        self.layout.setSpacing(0)
        self.layout.setContentsMargins( 0, 0, 0, 0)

        # Search button
        self.search_button = QtGui.QToolButton()
        self.search_button.setAutoRaise(True)
        self.search_button.setFocusPolicy(Qt.ClickFocus)
        self.search_button.setFixedHeight(self.get_height())
        self.search_button.clicked.connect(self.searchButtonClicked)
        self.search_button.setIcon(
            Icon('tango/16x16/actions/edit-clear.png').getQIcon()
        )
        self.search_button.setToolTip(unicode(_('clear')))

        # Open button
        self.open_button = QtGui.QToolButton()
        self.open_button.setAutoRaise(True)
        self.open_button.setFocusPolicy(Qt.ClickFocus)
        self.open_button.setFixedHeight(self.get_height())
        self.open_button.clicked.connect(self.openButtonClicked)
        self.open_button.setIcon( self.new_icon.getQIcon() )
        self.open_button.setToolTip(unicode(_('new')))

        # Search input
        self.search_input = DecoratedLineEdit(self)
        self.search_input.set_background_text(_('Search...'))
        self.search_input.textEdited.connect(self.textEdited)
        self.search_input.set_minimum_width( 20 )
        self.search_input.arrow_down_key_pressed.connect(self.on_arrow_down_key_pressed)
        # suppose garbage was entered, we need to refresh the content
        self.search_input.editingFinished.connect( self.search_input_editing_finished )
        self.setFocusProxy(self.search_input)

        # Search Completer
        self.completer = QtGui.QCompleter()
        self.completions_model = self.CompletionsModel(self.completer)
        self.completer.setModel(self.completions_model)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setCompletionMode(
            QtGui.QCompleter.UnfilteredPopupCompletion
        )
        #self.completer.activated.connect(self.completionActivated)
        #self.completer.highlighted.connect(self.completion_highlighted)
        self.completer.activated[QtCore.QModelIndex].connect(self.completionActivated)
        self.completer.highlighted[QtCore.QModelIndex].connect(self.completion_highlighted)
        self.search_input.setCompleter(self.completer)

        # Setup layout
        self.layout.addWidget(self.search_input)
        self.layout.addWidget(self.search_button)
        self.layout.addWidget(self.open_button)
        self.setLayout(self.layout)

    def set_field_attributes(self, editable = True, 
                                   background_color = None,
                                   tooltip = None, **kwargs):
        self.set_editable(editable)
        set_background_color_palette( self.search_input, background_color )
        self.search_input.setToolTip(unicode(tooltip or ''))

    def set_editable(self, editable):
        self._editable = editable
        self.search_input.setEnabled(editable)
        self.search_button.setEnabled(editable)

    def on_arrow_down_key_pressed(self):
        self.arrow_down_key_pressed.emit()

    def textEdited(self, text):
        self._last_highlighted_entity_getter = None
        text = self.search_input.user_input()

        def create_search_completion(text):
            return lambda: self.search_completions(text)

        post(
            create_search_completion(unicode(text)),
            self.display_search_completions
        )
        self.completer.complete()

    @model_function
    def search_completions(self, text):
        """Search for object that match text, to fill the list of completions

        :return: a list of tuples of (object_representation, object_getter)
        """
        search_decorator = create_entity_search_query_decorator(
            self.admin, text
        )
        if search_decorator:
            sresult = [
                (unicode(e), create_constant_function(e))
                for e in search_decorator(self.admin.entity.query).limit(20)
            ]
            return text, sresult
        return text, []

    def display_search_completions(self, prefix_and_completions):
        assert object_thread( self )
        prefix, completions = prefix_and_completions
        self.completions_model.setCompletions(completions)
        self.completer.setCompletionPrefix(prefix)
        self.completer.complete()

    def completionActivated(self, index):
        object_getter = index.data(Qt.EditRole)
        self.setEntity(variant_to_pyobject(object_getter))

    def completion_highlighted(self, index ):
        object_getter = index.data(Qt.EditRole)
        pyob = variant_to_pyobject(object_getter)
        self._last_highlighted_entity_getter = pyob

    def openButtonClicked(self):
        if self.entity_set:
            return self.createFormView()
        else:
            return self.createNew()

    def createSelectView(self):
        from camelot.view.action_steps.select_object import SelectDialog
        select_dialog = SelectDialog( self.admin, self )
        select_dialog.exec_()
        if select_dialog.object_getter != None:
            self.select_object( select_dialog.object_getter )
            
    def returnPressed(self):
        if not self.entity_set:
            self.createSelectView()

    def searchButtonClicked(self):
        if self.entity_set:
            self.setEntity(lambda:None)
        else:
            self.createSelectView()

    def trashButtonClicked(self):
        self.setEntity(lambda:None)

    def createNew(self):
        assert object_thread( self )

        @model_function
        def get_has_subclasses():
            return len(self.admin.get_subclass_tree())

        post(get_has_subclasses, self.show_new_view)

    def show_new_view(self, has_subclasses):
        assert object_thread( self )
        from camelot.view.workspace import show_top_level
        selected = QtGui.QDialog.Accepted
        admin = self.admin
        if has_subclasses:
            from camelot.view.controls.inheritance import SubclassDialog
            select_subclass = SubclassDialog(self, self.admin)
            select_subclass.setWindowTitle(_('select'))
            selected = select_subclass.exec_()
            admin = select_subclass.selected_subclass
        if selected:
            form = admin.create_new_view()
            form.entity_created_signal.connect( self.select_object )
            show_top_level( form, self )

    def createFormView(self):
        if self.entity_instance_getter:

            def get_admin_and_title():
                obj = self.entity_instance_getter()
                admin = self.admin.get_related_admin(obj.__class__)
                return admin, ''

            post(get_admin_and_title, self.show_form_view)

    def show_form_view(self, admin_and_title):
        from camelot.view.workspace import show_top_level
        admin, title = admin_and_title

        def create_collection_getter(instance_getter):
            return lambda:[instance_getter()]

        from camelot.view.proxy.collection_proxy import CollectionProxy

        model = CollectionProxy(
            admin,
            create_collection_getter(self.entity_instance_getter),
            admin.get_fields
        )
        model.dataChanged.connect(self.dataChanged)
        form = admin.create_form_view(title, model, 0)
        # @todo : dirty trick to keep reference
        #self.__form = form
        show_top_level( form, self )

    def dataChanged(self, index1, index2):
        self.setEntity(self.entity_instance_getter, False)

    def search_input_editing_finished(self):
        if not self.entity_set:
            # Only try to 'guess' what the user meant when no entity is set
            # to avoid inappropriate removal of data, (eg when the user presses
            # Esc, editingfinished will be called as well, and we should not
            # overwrite the current entity set)
            if self._last_highlighted_entity_getter:
                self.setEntity(self._last_highlighted_entity_getter)
            elif not self.entity_set and self.completions_model.rowCount()==1:
                # There is only one possible option
                index = self.completions_model.index(0,0)
                entity_getter = variant_to_pyobject(index.data(Qt.EditRole))
                self.setEntity(entity_getter)
        self.search_input.set_user_input(self._entity_representation)

    def set_value(self, value):
        """:param value: either ValueLoading, or a function that returns None
        or the entity to be shown in the editor"""
        self._last_highlighted_entity_getter = None
        value = CustomEditor.set_value(self, value)
        if value:
            self.setEntity(value, propagate = False)

    def get_value(self):
        """:return: a function that returns the selected entity or ValueLoading
        or None"""
        value = CustomEditor.get_value(self)
        if not value:
            value = self.entity_instance_getter
        return value

    @QtCore.pyqtSlot(tuple)
    def set_instance_representation(self, representation_and_propagate):
        """Update the gui"""
        ((desc, pk), propagate) = representation_and_propagate
        self._entity_representation = desc
        self.search_input.set_user_input(desc)

        if pk != False:
            self.open_button.setIcon(
                Icon('tango/16x16/places/folder.png').getQIcon()
            )
            self.open_button.setToolTip(unicode(_('open')))
            self.open_button.setEnabled(True)

            self.search_button.setIcon(
                Icon('tango/16x16/actions/edit-clear.png').getQIcon()
            )
            self.search_button.setToolTip(unicode(_('clear')))
            self.entity_set = True
        else:
            self.open_button.setIcon( self.new_icon.getQIcon() )
            self.open_button.setToolTip(unicode(_('new')))
            self.open_button.setEnabled(self._editable)

            self.search_button.setIcon( self.search_icon.getQIcon() )
            self.search_button.setToolTip(_('Search'))
            self.entity_set = False

        if propagate:
            self.editingFinished.emit()

    def setEntity(self, entity_instance_getter, propagate=True):
        self.entity_instance_getter = entity_instance_getter
        
        def get_instance_representation( entity_instance_getter, propagate ):
            """Get a representation of the instance

            :return: (unicode, pk) its unicode representation and its primary
            key or ('', False) if the instance was None"""
            
            entity = entity_instance_getter()
            if entity and hasattr(entity, 'id'):
                return ((unicode(entity), entity.id), propagate)
            elif entity:
                return ((unicode(entity), False), propagate)
            return ((None, False), propagate)

        post( update_wrapper( partial( get_instance_representation,
                                       entity_instance_getter,
                                       propagate ),
                              get_instance_representation ), 
              self.set_instance_representation)

    def select_object( self, entity_instance_getter ):
        self.setEntity(entity_instance_getter)
Example #11
0
class FileEditor(CustomEditor):
    """Widget for editing File fields"""

    filter = 'All files (*)'
    add_icon = Icon('tango/16x16/actions/list-add.png')
    open_icon = Icon('tango/16x16/actions/document-open.png')
    clear_icon = Icon('tango/16x16/actions/edit-delete.png')
    save_as_icon = Icon('tango/16x16/actions/document-save-as.png')
    document_pixmap = Icon('tango/16x16/mimetypes/x-office-document.png')

    def __init__(self,
                 parent=None,
                 storage=None,
                 field_name='file',
                 remove_original=False,
                 **kwargs):
        CustomEditor.__init__(self, parent)
        self.setObjectName(field_name)
        self.storage = storage
        self.filename = None  # the widget containing the filename
        self.value = None
        self.remove_original = remove_original
        self.setup_widget()

    def setup_widget(self):
        """Called inside init, overwrite this method for custom
        file edit widgets"""
        self.layout = QtGui.QHBoxLayout()
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)

        # Save As button
        self.save_as_button = QtGui.QToolButton()
        self.save_as_button.setFocusPolicy(Qt.ClickFocus)
        self.save_as_button.setIcon(self.save_as_icon.getQIcon())
        self.save_as_button.setToolTip(_('Save file as'))
        self.save_as_button.setAutoRaise(True)
        self.save_as_button.clicked.connect(self.save_as_button_clicked)

        # Clear button
        self.clear_button = QtGui.QToolButton()
        self.clear_button.setFocusPolicy(Qt.ClickFocus)
        self.clear_button.setIcon(self.clear_icon.getQIcon())
        self.clear_button.setToolTip(_('Delete file'))
        self.clear_button.setAutoRaise(True)
        self.clear_button.clicked.connect(self.clear_button_clicked)

        # Open button
        self.open_button = QtGui.QToolButton()
        self.open_button.setFocusPolicy(Qt.ClickFocus)
        self.open_button.setIcon(self.open_icon.getQIcon())
        self.open_button.setToolTip(_('Open file'))
        self.open_button.clicked.connect(self.open_button_clicked)
        self.open_button.setAutoRaise(True)

        # Add button
        self.add_button = QtGui.QToolButton()
        self.add_button.setFocusPolicy(Qt.StrongFocus)
        self.add_button.setIcon(self.add_icon.getQIcon())
        self.add_button.setToolTip(_('Attach file'))
        self.add_button.clicked.connect(self.add_button_clicked)
        self.add_button.setAutoRaise(True)

        # Filename
        self.filename = DecoratedLineEdit(self)
        self.filename.set_minimum_width(20)
        self.filename.setFocusPolicy(Qt.ClickFocus)

        # Search Completer
        #
        # Turn completion off, since it creates a thread per field on a form
        #
        # self.completer = QtGui.QCompleter()
        # self.completions_model = QtGui.QFileSystemModel()
        # self.completer.setCompletionMode(
        #    QtGui.QCompleter.UnfilteredPopupCompletion
        # )
        # self.completer.setModel( self.completions_model )
        # self.completer.activated[QtCore.QModelIndex].connect(self.file_completion_activated)
        # self.filename.setCompleter( self.completer )
        # settings = QtCore.QSettings()
        # last_path = settings.value('lastpath').toString()

        # # This setting of a rootPath causes a major delay on Windows, since
        # # the QFileSystemModel starts to fetch file information in a non-
        # # blocking way (although the documentation state the opposite).
        # # On Linux, there is no such delay, so it's safe to set such a root
        # # path and let the underlaying system start indexing.
        # import sys
        # if sys.platform != "win32":
        #    self.completions_model.setRootPath( last_path )

        # Setup layout
        self.document_label = QtGui.QLabel(self)
        self.document_label.setPixmap(self.document_pixmap.getQPixmap())
        self.layout.addWidget(self.document_label)
        self.layout.addWidget(self.filename)
        self.layout.addWidget(self.clear_button)
        self.layout.addWidget(self.open_button)
        self.layout.addWidget(self.add_button)
        self.layout.addWidget(self.save_as_button)
        self.setLayout(self.layout)

    def file_completion_activated(self, index):
        from camelot.view.storage import create_stored_file
        source_index = index.model().mapToSource(index)
        if not self.completions_model.isDir(source_index):
            path = self.completions_model.filePath(source_index)
            create_stored_file(
                self,
                self.storage,
                self.stored_file_ready,
                filter=self.filter,
                remove_original=self.remove_original,
                filename=path,
            )

    def set_value(self, value):
        value = CustomEditor.set_value(self, value)
        self.value = value
        if value:
            self.clear_button.setVisible(True)
            self.save_as_button.setVisible(True)
            self.open_button.setVisible(True)
            self.add_button.setVisible(False)
            self.filename.setText(value.verbose_name)
        else:
            self.clear_button.setVisible(False)
            self.save_as_button.setVisible(False)
            self.open_button.setVisible(False)
            self.add_button.setVisible(True)
            self.filename.setText('')
        return value

    def get_value(self):
        return CustomEditor.get_value(self) or self.value

    def set_field_attributes(self,
                             editable=True,
                             background_color=None,
                             tooltip=None,
                             remove_original=False,
                             **kwargs):
        self.set_enabled(editable)
        if self.filename:
            set_background_color_palette(self.filename, background_color)
            self.filename.setToolTip(unicode(tooltip or ''))
        self.remove_original = remove_original

    def set_enabled(self, editable=True):
        self.clear_button.setEnabled(editable)
        self.add_button.setEnabled(editable)
        self.filename.setEnabled(editable)
        self.filename.setReadOnly(not editable)
        self.document_label.setEnabled(editable)
        self.setAcceptDrops(editable)

    def stored_file_ready(self, stored_file):
        """Slot to be called when a new stored_file has been created by
        the storage"""
        self.set_value(stored_file)
        self.editingFinished.emit()

    def save_as_button_clicked(self):
        from camelot.view.storage import save_stored_file
        value = self.get_value()
        if value:
            save_stored_file(self, value)

    def add_button_clicked(self):
        from camelot.view.storage import create_stored_file
        create_stored_file(
            self,
            self.storage,
            self.stored_file_ready,
            filter=self.filter,
            remove_original=self.remove_original,
        )

    def open_button_clicked(self):
        from camelot.view.storage import open_stored_file
        open_stored_file(self, self.value)

    def clear_button_clicked(self):
        answer = QtGui.QMessageBox.question(
            self, _('Remove this file ?'),
            _('If you continue, you will no longer be able to open this file.'
              ), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
        if answer == QtGui.QMessageBox.Yes:
            self.value = None
            self.editingFinished.emit()

    #
    # Drag & Drop
    #
    def dragEnterEvent(self, event):
        event.acceptProposedAction()

    def dragMoveEvent(self, event):
        event.acceptProposedAction()

    def dropEvent(self, event):
        from camelot.view.storage import create_stored_file
        if event.mimeData().hasUrls():
            url = event.mimeData().urls()[0]
            filename = url.toLocalFile()
            if filename:
                create_stored_file(
                    self,
                    self.storage,
                    self.stored_file_ready,
                    filter=self.filter,
                    remove_original=self.remove_original,
                    filename=filename,
                )