def setTracks(self, tracks): if not self._first_display: self._removeOldWidgets() self._first_display = False self._widgets = {} parent = QWidget(self) l = QVBoxLayout(parent) for track in tracks: w = self._createTrackProgress(parent, track) l.addWidget(w) self._widgets[track['id']] = w l.addStretch(1) parent.setLayout(l) scroll = QScrollArea(self) scroll.setWidgetResizable(True) scroll.setWidget(parent) scroll.setAlignment(Qt.AlignCenter) scroll.setFrameShape(QFrame.NoFrame); l = QVBoxLayout(self) l.addWidget(scroll) l.addWidget(self.close_button) self.setLayout(l) self.close_button.setEnabled(False)
def create_scrolled_panel ( self, parent ): """ Returns a panel that can scroll its contents. """ sa = QScrollArea( check_parent( parent ) ) sa.setFrameShape( QFrame.NoFrame ) sa.setWidgetResizable( True ) return control_adapter_for( sa )
def create_doc_tab_populate_combobox(self): """ Creates the supporting document component widget. """ self.doc_tab_data() self.docs_tab = QTabWidget() self.docs_tab_index = OrderedDict() for i, (id, doc) in enumerate(self.doc_types.iteritems()): self.docs_tab_index[doc] = i # the tab widget containing the document widget layout # and the child of the tab. tab_widget = QWidget() tab_widget.setObjectName(doc) # The layout of the tab widget cont_layout = QVBoxLayout(tab_widget) cont_layout.setObjectName(u'widget_layout_{}'.format(doc)) # the scroll area widget inside the tab widget. scroll_area = QScrollArea(tab_widget) scroll_area.setFrameShape(QFrame.NoFrame) scroll_area.setObjectName(u'tab_scroll_area_{}'.format(doc)) layout_widget = QWidget() # the widget the is under the scroll area content and # the widget containing the document widget layout # This widget is hidden and shown based on the STR number layout_widget.setObjectName(u'widget_{}'.format(doc)) doc_widget_layout = QVBoxLayout(layout_widget) doc_widget_layout.setObjectName( u'doc_widget_layout_{}'.format(doc)) doc_widget = QWidget() doc_widget.setObjectName(u'doc_widget_{}_{}'.format( doc, self.str_number)) doc_widget_layout.addWidget(doc_widget) # the layout containing document widget. ### This is the layout that is registered to add uploaded # supporting documents widgets into. tab_layout = QVBoxLayout(doc_widget) tab_layout.setObjectName(u'layout_{}_{}'.format( doc, self.str_number)) scroll_area.setWidgetResizable(True) scroll_area.setWidget(layout_widget) cont_layout.addWidget(scroll_area) # Add the tab widget with the document # type name to create a tab. self.docs_tab.addTab(tab_widget, doc) self.container_box.addWidget(self.docs_tab, 1) if len(self.str_numbers) == 1: self.doc_type_cbo.addItem(doc, id)
def _createCategoryView(self): self._clearCurrentView() self._currentToolBox = QToolBox(self) self._currentToolBox.setAutoFillBackground(False) for category in self._action.getPrivacyCategories(): self._createAndInsertSingleView(category, -1) peerExceptions = SingleCategoryView(self._action, self._currentToolBox, self.logger, category=None, mode=PrivacySettings.POLICY_PEER_EXCEPTION) self._currentSingleViews[self._PEER_EXCEPTIONS_VIEW] = peerExceptions self._currentToolBox.addItem(peerExceptions, "Special Peers") w = QScrollArea(self) w.setAutoFillBackground(False) w.viewport().setAutoFillBackground(False) w.setWidgetResizable(True) w.setWidget(self._currentToolBox) w.setFrameShape(QFrame.NoFrame) self._settingsWidget.layout().addWidget(w)
class _DocumentTypeContainer(QWidget): """ Container for a single document type. """ def __init__(self, parent=None): QWidget.__init__(self, parent) self._gl = QGridLayout(self) self._sa = QScrollArea(self) self._sa.setFrameShape(QFrame.NoFrame) self._sa.setWidgetResizable(True) self._sa_content_area = QWidget() self._vl_ca = QVBoxLayout(self._sa_content_area) self._vl_ca.setSpacing(0) self._vl_ca.setMargin(0) self._doc_container = QVBoxLayout() self._vl_ca.addLayout(self._doc_container) self._sa.setWidget(self._sa_content_area) self._gl.addWidget(self._sa, 0, 0, 1, 1) @property def container(self): return self._doc_container
class _DocumentTypeContainer(QWidget): """ Container for a single document type. """ def __init__(self, parent=None): QWidget.__init__(self, parent) self._gl = QGridLayout(self) self._sa = QScrollArea(self) self._sa.setFrameShape(QFrame.NoFrame) self._sa.setWidgetResizable(True) self._sa_content_area = QWidget() self._vl_ca = QVBoxLayout(self._sa_content_area) self._vl_ca.setSpacing(0) self._vl_ca.setMargin(0) self._doc_container = QVBoxLayout() self._vl_ca.addLayout(self._doc_container) self._sa.setWidget(self._sa_content_area) self._gl.addWidget(self._sa, 0, 0, 1, 1) @property def container(self): return self._doc_container
class PlotDialog(QDialog, PlotManager): """ Implements a dialog to which an arbitrary number of plots can be added. This class implements a `QDialog` with a number of plots on it. The axes of the plots can be arbitrarily synchronized and option checkboxes can be added which provide callbacks when the checkbox state changes. :param str wintitle: Title of the window. :param bool major_grid: Show major grid in plots. :param bool minor_grid: Show minor grid in plots. :param bool toolbar: Show toolbar. :param parent: Parent window. :param panels: A list of guiqwt panels to be added to the window. :param int min_plot_width: Default minimum width for plots. :param int min_plot_height: Default minimum height for plots. """ def __init__(self, wintitle='Plot window', major_grid=True, minor_grid=False, toolbar=True, parent=None, panels=None, min_plot_width=100, min_plot_height=75): QDialog.__init__(self, parent) self.setWindowFlags(Qt.Window) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(_fromUtf8(':/Application/Main')), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.setWindowIcon(icon) self.major_grid = major_grid self.minor_grid = minor_grid self.min_plot_width = min_plot_width self.min_plot_height = min_plot_height # WidgetMixin copy PlotManager.__init__(self, main=self) self.main_layout = QVBoxLayout(self) self.color_layout = QHBoxLayout() self.plot_layout = QGridLayout() self.plot_layout.setMargin(0) self.plot_scroll_widget = QWidget() self.plot_scroll_area = QScrollArea() self.plot_scroll_area.setFrameShape(QFrame.NoFrame) self.plot_scroll_area.setWidgetResizable(True) self.option_layout = QHBoxLayout() self.plot_widget = None if panels is not None: for panel in panels: self.add_panel(panel) self.toolbar = QToolBar('Tools') if not toolbar: self.toolbar.hide() # Configuring widget layout self._setup_widget_properties(wintitle=wintitle, icon=icon) self._setup_widget_layout() # Options self.option_callbacks = {} self.legend = None self.axis_syncplots = {} def _setup_widget_properties(self, wintitle, icon): self.setWindowTitle(wintitle) if isinstance(icon, basestring): icon = get_icon(icon) self.setWindowIcon(icon) self.setMinimumSize(320, 240) self.resize(720, 540) def _setup_widget_layout(self): self.main_layout.addWidget(self.toolbar) self.main_layout.addLayout(self.color_layout) self.main_layout.addWidget(self.plot_scroll_area) self.plot_scroll_area.setWidget(self.plot_scroll_widget) self.plot_scroll_widget.setLayout(self.plot_layout) self.main_layout.addLayout(self.option_layout) self.setLayout(self.main_layout) def add_custom_curve_tools(self, antialiasing=True, activate_zoom=True, signal_stats=False): """ Adds typically needed curve tools to the window. :param bool antialiasing: Determines if the antialiasing tool is added. :param bool activate_zoom: Determines if the zoom tool is activated initially (otherwise, the selection tool will be activated). :param bool signal_stats: Determines if the signal stats tool is available. """ self.add_toolbar(self.toolbar) t = self.add_tool(SelectTool) if not activate_zoom: self.set_default_tool(t) self.add_tool(BasePlotMenuTool, "item") self.add_tool(ExportItemDataTool) try: # Old versions of guiqwt and spyderlib do not support this import spyderlib.widgets.objecteditor from guiqwt.tools import EditItemDataTool self.add_tool(EditItemDataTool) except ImportError: pass self.add_tool(ItemCenterTool) self.add_tool(DeleteItemTool) self.add_separator_tool() t = self.add_tool(RectZoomTool) if activate_zoom: self.set_default_tool(t) self.add_tool(guiqwt_tools.HomeTool) self.add_tool(guiqwt_tools.PanTool) self.add_separator_tool() self.add_tool(BasePlotMenuTool, "grid") self.add_tool(BasePlotMenuTool, "axes") self.add_tool(DisplayCoordsTool) if self.get_itemlist_panel(): self.add_tool(ItemListPanelTool) if signal_stats: self.add_separator_tool() self.add_tool(SignalStatsTool) if antialiasing: self.add_tool(AntiAliasingTool) self.add_tool(AxisScaleTool) self.add_separator_tool() self.add_tool(SaveAsTool) self.add_tool(CopyToClipboardTool) self.add_tool(PrintTool) self.add_tool(guiqwt_tools.HelpTool) self.add_separator_tool() self.get_default_tool().activate() def add_custom_image_tools(self, activate_zoom=True): """ Adds typically needed image tools to the window. """ self.add_toolbar(self.toolbar) t = self.add_tool(SelectTool) if not activate_zoom: self.set_default_tool(t) self.add_tool(BasePlotMenuTool, "item") self.add_tool(ExportItemDataTool) try: # Old versions of guiqwt and spyderlib do not support this import spyderlib.widgets.objecteditor from guiqwt.tools import EditItemDataTool self.add_tool(EditItemDataTool) except ImportError: pass self.add_tool(ItemCenterTool) self.add_tool(DeleteItemTool) self.add_separator_tool() t = self.add_tool(RectZoomTool) if activate_zoom: self.set_default_tool(t) self.add_tool(guiqwt_tools.HomeTool) self.add_tool(guiqwt_tools.PanTool) self.add_separator_tool() self.add_tool(BasePlotMenuTool, "grid") self.add_tool(BasePlotMenuTool, "axes") self.add_tool(DisplayCoordsTool) if self.get_itemlist_panel(): self.add_tool(ItemListPanelTool) self.add_separator_tool() self.add_tool(ColormapTool) self.add_tool(ReverseYAxisTool) self.add_tool(AspectRatioTool) if self.get_contrast_panel(): self.add_tool(ContrastPanelTool) if self.get_xcs_panel() and self.get_ycs_panel(): self.add_tool(XCSPanelTool) self.add_tool(YCSPanelTool) self.add_tool(CrossSectionTool) self.add_tool(AverageCrossSectionTool) self.add_separator_tool() self.add_tool(SaveAsTool) self.add_tool(CopyToClipboardTool) self.add_tool(PrintTool) self.add_tool(guiqwt_tools.HelpTool) self.add_separator_tool() self.get_default_tool().activate() def add_option(self, name, change_callback, active=False): """ Add an option (using a checkbox) to the window. :param str name: The name of the option. :param func change_callback: A function accepting the new state as a parameter. The function will be called whenever the state of the option changes. :param bool active: Determines if the option is activated initially. """ checkBox = QtGui.QCheckBox(self) checkBox.setChecked(active) checkBox.setText(name) checkBox.stateChanged.connect(self._option_callback) self.option_callbacks[checkBox] = change_callback self.option_layout.addWidget(checkBox) def add_x_synchronization_option(self, active, ids=None): """ Offer an option for X axes synchronization. This method should be called after show(), so that a proper initial synchronization can be performed. :param bool active: Determines whether the axes are synchronized initially. :param sequence ids: List of plot ids to synchronize. """ self.axis_syncplots[BasePlot.X_BOTTOM] = ids if active and ids: self.synchronize_axis(BasePlot.X_BOTTOM) self.add_option('Synchronize X Axes', PlotDialog._synchronization_option_x, active) def add_y_synchronization_option(self, active, ids=None): """ Offer an option for Y axes synchronization. This method should be called after show(), so that a proper initial synchronization can be performed. :param bool active: Determines whether the axes are synchronized initially :param sequence ids: List of plot ids to synchronize. """ self.axis_syncplots[BasePlot.Y_LEFT] = ids if active and ids: self.synchronize_axis(BasePlot.Y_LEFT) self.add_option('Synchronize Y Axes', PlotDialog._synchronization_option_y, active) def _synchronization_option_x(self, state): """ Callback for x-axis synchronization """ if state: self.synchronize_axis(BasePlot.X_BOTTOM) else: self.unsynchronize_axis(BasePlot.X_BOTTOM) def _synchronization_option_y(self, state): """ Callback for y-axis synchronization """ if state: self.synchronize_axis(BasePlot.Y_LEFT) else: self.unsynchronize_axis(BasePlot.Y_LEFT) def replace_colors(self, replace_list): """ Replace colors of items in all plots. This can be useful when changing the background color to black and black items should be drawn in white: ``replace_colors([('#000000', '#ffffff']))`` :param list replace_list: A list of tuples of Qt color names. The first color in each tuple is replaced by the second color. """ for plot in self.plots.itervalues(): for i in plot.get_items(): if isinstance(i, CurveItem): pen = i.pen() elif isinstance(i, Marker): pen = i.linePen() else: continue for color in replace_list: c1 = QColor(color[0]) c2 = QColor(color[1]) if pen.color() != c1: continue pen.setColor(c2) break if isinstance(i, CurveItem): i.setPen(pen) elif isinstance(i, Marker): i.setLinePen(pen) plot.replot() def set_background_color(self, color): """ Set the background color for all plots. :param str color: A Qt color name (e.g. '#ff0000') """ for p in self.plots.itervalues(): p.setCanvasBackground(QColor(color)) p.replot() def add_unit_color(self, color, name='Unit color:'): """ Create a small legend on top of the window with only one entry. :param str color: A Qt color name (e.g. '#ff0000') :param str name: The name of the legend item. It will be displayed on the left of the color. """ label = QtGui.QLabel(self) label.setText(name) label.setAlignment(Qt.AlignRight) self.color_layout.addWidget(label) label = QtGui.QLabel(self) label.setStyleSheet('background-color:' + str(color)) label.setFrameShape(QFrame.StyledPanel) label.setMaximumWidth(80) self.color_layout.addWidget(label) def add_custom_label(self, legend_string): """ Add a label on the right of the plots :param str legend_string: An arbitrary string (which can contain newlines) to display on the right of the plots """ label = QtGui.QLabel(self) label.setText(legend_string) self.plot_layout.addWidget( label, 0, self.plot_layout.columnCount(), -1, 1) def add_color_legend(self, legend, show_option=None): """ Create a legend on the right of the plots with colors and names. :param sequence legend: List of (color, text) tuples, where `color` is a Qt color name (e.g. '#ff0000') and `text` is the corresponding text to display in the legend. :param bool show_option: Determines whether a toggle for the legend will be shown (if the parameter is not ``None``) and if the legend is visible initially (``True``/``False``). """ widget = QWidget(self) layout = QGridLayout(widget) widget.setLayout(layout) for l in legend: label = QtGui.QLabel(self) label.setStyleSheet('background-color:' + str(l[0])) label.setFrameShape(QFrame.StyledPanel) label.setMaximumWidth(80) label.setMaximumHeight(12) layout.addWidget(label, layout.rowCount(), 0, 1, 1) label = QtGui.QLabel(self) label.setText(l[1]) layout.addWidget(label, layout.rowCount() - 1, 1, 1, 1) self.plot_layout.addWidget( widget, 0, self.plot_layout.columnCount(), -1, 1) if show_option is not None: widget.setVisible(show_option) self.add_option( 'Show Legend Sidebar', lambda w, s: widget.setVisible(s), show_option) def add_legend_option(self, legends, active): """ Create a user option to show or hide a list of legend objects. :param sequence legends: The legend objects affected by the option. :param bool active: Determines whether the legends will be visible initially. """ self.legends = legends self._set_legend_visibility(active) self.add_option('Show legend', self._legend_callback, active) if active: self._set_legend_visibility(True) def _option_callback(self, state): self.option_callbacks[self.sender()](self, state) #noinspection PyUnusedLocal def _legend_callback(self, win, state): self._set_legend_visibility(state > 0) def _set_legend_visibility(self, visible): for p in self.plots.itervalues(): for l in self.legends: p.set_item_visible(l, visible) def add_plot_widget(self, plot_widget, plot_id, row=-1, column=0, min_plot_width=None, min_plot_height=None): """ Adds a guiqwt plot to the window. :param plot_widget: The plot to add. :type plot_widget: guiqwt plot widget :param int plot_id: The id of the new plot. :param int row: The row of the new plot. If this is -1, the new plot will be added in a new row (if `column` is 0) or in the last row. :param int column: The column of the new plot. :param int min_plot_width: The minimum width of this plot. If ``None``, the default minimum width for this dialog is used. :param int max_plot_height: The minimum height of this plot. If ``None``, the default minimum height for this dialog is used. """ if row == -1: if column == 0: row = self.plot_layout.rowCount() else: row = self.plot_layout.rowCount() - 1 pw = min_plot_width if pw is None: pw = self.min_plot_width ph = min_plot_height if ph is None: ph = self.min_plot_height plot_widget.setMinimumSize(pw, ph) self.plot_layout.addWidget(plot_widget, row, column) new_plot = plot_widget.plot self.add_plot(new_plot, plot_id) def synchronize_axis(self, axis, plots=None): if plots is None: if axis in self.axis_syncplots: plots = self.axis_syncplots[axis] else: plots = self.plots.keys() if len(plots) < 1: return PlotManager.synchronize_axis(self, axis, plots) # Find interval that needs to be shown in order to include all # currently shown parts in the synchronized plots plot_objects = [self.plots[p] for p in plots] lb = min((p.axisScaleDiv(axis).lowerBound() for p in plot_objects)) ub = max((p.axisScaleDiv(axis).upperBound() for p in plot_objects)) for p in plot_objects: p.setAxisScale(axis, lb, ub) p.replot() def unsynchronize_axis(self, axis, plots=None): if plots is None: if axis in self.axis_syncplots: plots = self.axis_syncplots[axis] else: plots = self.plots.keys() for plot_id in plots: if not plot_id in self.synchronized_plots: continue synclist = self.synchronized_plots[plot_id] for plot2_id in plots: if plot_id == plot2_id: continue item = (axis, plot2_id) if item in synclist: synclist.remove(item) def plot_axis_changed(self, plot): ids = [k for k, p in self.plots.iteritems() if p == plot] if len(ids) < 1: return plot_id = ids[0] if plot_id not in self.synchronized_plots: return for (axis, other_plot_id) in self.synchronized_plots[plot_id]: scalediv = plot.axisScaleDiv(axis) other = self.get_plot(other_plot_id) lb = scalediv.lowerBound() ub = scalediv.upperBound() other.setAxisScale(axis, lb, ub) other.replot() def set_plot_title(self, plot, title): """ Set the title of a guiqwt plot and use the same font as for the rest of the window. :param plot: The plot for which the title is set. :param str title: The new title of the plot. """ plot.setTitle(title) l = plot.titleLabel() l.setFont(self.font()) plot.setTitle(l.text()) def show(self): for p in self.plots.itervalues(): if not self.minor_grid: p.grid.gridparam.min_xenabled = False p.grid.gridparam.min_yenabled = False if not self.major_grid: p.grid.gridparam.maj_xenabled = False p.grid.gridparam.maj_yenabled = False p.grid.update_params() super(PlotDialog, self).show()
class DHakkinda(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.resize(500, 350) self.setMaximumSize(500, 350) self.gLayout =QGridLayout(self) self.logo = QLabel(self) self.logo.setPixmap(QPixmap(":/logo/data/logo.png")) self.gLayout.addWidget(self.logo, 0, 0, 2, 1) self.appName = QLabel(self) font = QFont() font.setPointSize(32) font.setWeight(50) self.appName.setFont(font) self.gLayout.addWidget(self.appName, 0, 1, 1, 2) self.appVersion = QLabel(self) font = QFont() font.setPointSize(9) font.setWeight(75) font.setBold(True) self.appVersion.setFont(font) self.appVersion.setAlignment(Qt.AlignHCenter|Qt.AlignTop) self.gLayout.addWidget(self.appVersion, 1, 1, 1, 2) self.gBox = QGroupBox(self) font = QFont() font.setPointSize(12) font.setWeight(75) font.setBold(True) self.gBox.setFont(font) self.gLayout2 = QGridLayout(self.gBox) self.scrollArea = QScrollArea(self.gBox) self.scrollArea.setFrameShape(QFrame.NoFrame) self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents.setGeometry(0, 0, 476, 199) self.gLayout3 = QGridLayout(self.scrollAreaWidgetContents) self.appHakkinda = QLabel(self.scrollAreaWidgetContents) font = QFont() font.setPointSize(9) font.setWeight(50) font.setBold(False) self.appHakkinda.setFont(font) self.appHakkinda.setWordWrap(True) self.gLayout3.addWidget(self.appHakkinda, 0, 0, 1, 1) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.gLayout2.addWidget(self.scrollArea, 0, 0, 1, 1) self.gLayout.addWidget(self.gBox, 2, 0, 2, 4) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.gLayout.addItem(spacerItem, 0, 3, 1, 1) spacerItem1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.gLayout.addItem(spacerItem1, 2, 1, 1, 2) self.setWindowTitle(u"Virux Hakkında") self.appName.setText(u"Virux") self.appVersion.setText(u"Sürüm %s"%QApplication.applicationVersion()) self.gBox.setTitle(u"Hakkında") self.appHakkinda.setText(u""" <p>Virux, platform bağımsız bir antivirüs yazılımıdır :P</p> <p>Yazılımıın bir arayüzü yoktur. Sadece sistem çubuğunda bir tepsi oluşur. Bu tepsi animasyon şeklindedir.</p> <p>Rasgele zamanlarda mevcut olan dialoglardan bir tanesi ekranda gözükecektir. Sadece eğlence amacıyla yapılmıştır...</p> <p><b>Geliştirici:</b> Metehan Özbek - <a href='mailto:[email protected]'>[email protected]</a></p> <p><b>Görsel Çalışma:</b> Yasin Özcan - <a href='mailto:[email protected]'>[email protected]</a></p> <p><b>Katkı Yapanlar:</b> Yaşar Arabacı - <a href='mailto:[email protected]'>[email protected]</a></p> <p><b>Lisans:</b> GPL v3</p> <p></p>""")
class EntityEditorDialog(QDialog, MapperMixin): """ Dialog for editing entity attributes. """ addedModel = pyqtSignal(object) def __init__( self, entity, model=None, parent=None, manage_documents=True, collect_model=False, parent_entity=None, exclude_columns=[], plugin = None ): """ Class constructor. :param entity: Entity object corresponding to a table object. :type entity: Entity :param model: Data object for loading data into the form widgets. If the model is set, then the editor dialog is assumed to be in edit mode. :type model: object :param parent: Parent widget that the form belongs to. :type parent: QWidget :param manage_documents: True if the dialog should provide controls for managing supporting documents. Only applicable if the entity allows for supporting documents to be attached. :type manage_documents: bool :param collect_model: If set to True only returns the filled form model without saving it to the database. :type collect_model: Boolean :param parent_entity: The parent entity of the editor :type parent_entity: Object :param exclude_columns: List of columns to be excluded if in a list. :type exclude_columns: List :return: If collect_model, returns SQLAlchemy Model """ QDialog.__init__(self, parent) self.entity_table_model = {} self.collection_suffix = self.tr('Collection') #Set minimum width self.setMinimumWidth(450) self.plugin = plugin #Flag for mandatory columns self.has_mandatory = False self.reload_form = False self._entity = entity self.edit_model = model self.column_widgets = OrderedDict() self._parent = parent self.exclude_columns = exclude_columns self.entity_tab_widget = None self._disable_collections = False self.filter_val = None self.parent_entity = parent_entity self.child_models = OrderedDict() self.entity_scroll_area = None self.entity_editor_widgets = OrderedDict() # Set notification layout bar self.vlNotification = QVBoxLayout() self.vlNotification.setObjectName('vlNotification') self._notifBar = NotificationBar(self.vlNotification) self.do_not_check_dirty = False # Set manage documents only if the entity supports documents if self._entity.supports_documents: self._manage_documents = manage_documents else: self._manage_documents = False # Setup entity model self._ent_document_model = None if self._entity.supports_documents: self.ent_model, self._ent_document_model = entity_model( self._entity, with_supporting_document=True ) else: self.ent_model = entity_model(self._entity) if not model is None: self.ent_model = model MapperMixin.__init__(self, self.ent_model, entity) self.collect_model = collect_model self.register_column_widgets() try: if isinstance(parent._parent, EntityEditorDialog): # hide collections form child editor self._disable_collections = True except AttributeError: self._parent._parent = None # Set title editor_trans = self.tr('Editor') if self._entity.label is not None: if self._entity.label != '': title_str = self._entity.label else: title_str = format_name(self._entity.short_name) else: title_str = format_name(self._entity.short_name) self.title = u'{0} {1}'.format(title_str, editor_trans) self.setWindowTitle(self.title) self._init_gui() self.adjustSize() self._get_entity_editor_widgets() if isinstance(parent._parent, EntityEditorDialog): self.parent_entity = parent.parent_entity self.set_parent_values() # make the size smaller to differentiate from parent and as it # only has few tabs. self.adjustSize() self.attribute_mappers = self._attr_mapper_collection # Exception title for editor extension exceptions self._ext_exc_msg = self.tr( 'An error has occured while executing Python code in the editor ' 'extension:' ) # Register custom editor extension if specified self._editor_ext = entity_dlg_extension(self) if not self._editor_ext is None: self._editor_ext.post_init() # Initialize CascadingFieldContext objects self._editor_ext.connect_cf_contexts() def _init_gui(self): # Setup base elements self.gridLayout = QGridLayout(self) self.gridLayout.setObjectName('glMain') self.gridLayout.addLayout( self.vlNotification, 0, 0, 1, 1 ) QApplication.processEvents() #set widgets values column_widget_area = self._setup_columns_content_area() self.gridLayout.addWidget( column_widget_area, 1, 0, 1, 1 ) QApplication.processEvents() # Add notification for mandatory columns if applicable next_row = 2 if self.has_mandatory: self.required_fields_lbl = QLabel(self) msg = self.tr( 'Please fill out all required (*) fields.' ) msg = self._highlight_asterisk(msg) self.required_fields_lbl.setText(msg) self.gridLayout.addWidget( self.required_fields_lbl, next_row, 0, 1, 2 ) # Bump up row reference next_row += 1 self.buttonBox = QDialogButtonBox(self) self.buttonBox.setObjectName('buttonBox') self.gridLayout.addWidget( self.buttonBox, next_row, 0, 1, 1 ) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons( QDialogButtonBox.Cancel|QDialogButtonBox.Save ) if self.edit_model is None: if not self.collect_model: self.save_new_button = QPushButton( QApplication.translate( 'EntityEditorDialog', 'Save and New' ) ) self.buttonBox.addButton( self.save_new_button, QDialogButtonBox.ActionRole ) # edit model, collect model # adding new record for child # Saving in parent editor if not isinstance(self._parent._parent, EntityEditorDialog): # adding a new record if self.edit_model is None: # saving when digitizing. if self.collect_model: self.buttonBox.accepted.connect(self.on_model_added) # saving parent editor else: self.buttonBox.accepted.connect(self.save_parent_editor) self.save_new_button.clicked.connect(self.save_and_new) # updating existing record else: if not self.collect_model: # updating existing record of the parent editor self.buttonBox.accepted.connect(self.save_parent_editor) else: self.buttonBox.accepted.connect(self.on_model_added) # Saving in child editor else: # save and new record if self.edit_model is None: self.buttonBox.accepted.connect(self.on_child_saved) self.save_new_button.clicked.connect( lambda: self.on_child_saved(True) ) else: # When updating an existing child editor save to the db self.buttonBox.accepted.connect( self.on_child_saved ) #self.buttonBox.accepted.connect(self.submit) self.buttonBox.rejected.connect(self.cancel) @property def notification_bar(self): """ :return: Returns the dialog's notification bar. :rtype: NotificationBar """ return self._notifBar def save_parent_editor(self): """ Saves the parent editor and its children. """ self.submit() self.save_children() def set_parent_values(self): """ Sets the parent display column for the child. """ if self.parent_entity is None: return for col in self._entity.columns.values(): if col.TYPE_INFO == 'FOREIGN_KEY': parent_entity = col.parent if parent_entity == self.parent_entity: self.parent_widgets_value_setter(self._parent._parent, col) def parent_widgets_value_setter(self, parent, col): """ Finds and sets the value from parent widget and set it to the column widget of a child using the child column. :param parent: The parent widget :type parent: QWidget :param col: The child column object :type col: Object """ for parent_col, parent_widget in parent.column_widgets.iteritems(): if parent_col.name == col.name: self.single_parent_value_setter(col, parent_widget) break if parent_col.name in col.entity_relation.display_cols: self.single_parent_value_setter(col, parent_widget) break def single_parent_value_setter(self, col, parent_widget): """ Gets value from parent widget and set it to the column widget of a child using the child column. :param parent: The parent widget :type parent: QWidget :param col: The child column object :type col: Object """ local_widget = self.column_widgets[col] local_widget.show_clear_button() self.filter_val = parent_widget.text() local_widget.setText(self.filter_val) def save_and_new(self): """ A slot raised when Save and New button is click. It saves the form without showing a success message. Then it sets reload_form property to True so that entity_browser can re-load the form. """ from stdm.ui.entity_browser import ( EntityBrowserWithEditor ) self.submit(False, True) self.save_children() if self.is_valid: self.addedModel.emit(self.model()) self.setModel(self.ent_model()) self.clear() self.child_models.clear() for index in range(0, self.entity_tab_widget.count()-1): if isinstance( self.entity_tab_widget.widget(index), EntityBrowserWithEditor ): child_browser = self.entity_tab_widget.widget(index) child_browser.remove_rows() def on_model_added(self): """ A slot raised when a form is submitted with collect model set to True. There will be no success message and the form does not close. """ self.submit(True) self.addedModel.emit(self.model()) def closeEvent(self, event): ''' Raised when a request to close the window is received. Check the dirty state of input controls and prompt user to save if dirty. ''' if self.do_not_check_dirty: event.accept() return isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: # We need to ignore the event so that validation and # saving operations can be executed event.ignore() self.submit() elif userResponse == QMessageBox.No: event.accept() elif userResponse == QMessageBox.Cancel: event.ignore() else: event.accept() def on_child_saved(self, save_and_new=False): """ A slot raised when the save or save and new button is clicked. It sets the child_models dictionary of the parent when saved. :param save_and_new: A boolean indicating the save and new button is clicked to trigger the slot. :type save_and_new: Boolean """ if self.parent_entity is None: return self.submit(True) insert_pos = self._parent.tbEntity.model().rowCount() + 1 # Save to parent editor so that it is persistent. self._parent._parent.child_models[insert_pos, self._entity] = \ self.model() self.addedModel.emit(self.model()) if not save_and_new: self.accept() else: if self.is_valid: #self.addedModel.emit(self.model()) self.setModel(self.ent_model()) self.clear() self.set_parent_values() def save_children(self): """ Saves children models into the database by assigning the the id of the parent for foreign key column. """ if len(self.child_models) < 1: return children_obj = [] for row_entity, model in self.child_models.iteritems(): row_pos = row_entity[0] entity = row_entity[1] ent_model = entity_model(entity) entity_obj = ent_model() for col in entity.columns.values(): if col.TYPE_INFO == 'FOREIGN_KEY': if col.parent.name == self._entity.name: setattr(model, col.name, self.model().id) children_obj.append(model) entity_obj.saveMany(children_obj) def register_column_widgets(self): """ Registers the column widgets. """ # Append column labels and widgets table_name = self._entity.name columns = table_column_names(table_name) self.scroll_widget_contents = QWidget() self.scroll_widget_contents.setObjectName( 'scrollAreaWidgetContents' ) for c in self._entity.columns.values(): if c.name in self.exclude_columns: continue if not c.name in columns and not isinstance(c, VirtualColumn): continue # Get widget factory column_widget = ColumnWidgetRegistry.create( c, self.scroll_widget_contents, host=self ) self.column_widgets[c] = column_widget def _setup_columns_content_area(self): # Only use this if entity supports documents # self.entity_tab_widget = None self.doc_widget = None self.entity_scroll_area = QScrollArea(self) self.entity_scroll_area.setFrameShape(QFrame.NoFrame) self.entity_scroll_area.setWidgetResizable(True) self.entity_scroll_area.setObjectName('scrollArea') # Grid layout for controls self.gl = QGridLayout(self.scroll_widget_contents) self.gl.setObjectName('gl_widget_contents') # Append column labels and widgets table_name = self._entity.name columns = table_column_names(table_name) # Iterate entity column and assert if they exist row_id = 0 for c, column_widget in self.column_widgets.iteritems(): if c.name in self.exclude_columns: continue if not c.name in columns and not isinstance(c, VirtualColumn): continue if column_widget is not None: header = c.ui_display() self.c_label = QLabel(self.scroll_widget_contents) # Format label text if it is a mandatory field if c.mandatory: header = u'{0} *'.format(c.ui_display()) #Highlight asterisk header = self._highlight_asterisk(header) self.c_label.setText(header) self.gl.addWidget(self.c_label, row_id, 0, 1, 1) self.column_widget = column_widget self.gl.addWidget(self.column_widget, row_id, 1, 1, 1) #Add user tip if specified for the column configuration if c.user_tip: self.tip_lbl = UserTipLabel(user_tip=c.user_tip) self.gl.addWidget(self.tip_lbl, row_id, 2, 1, 1) if c.mandatory and not self.has_mandatory: self.has_mandatory = True col_name = c.name #Replace name accordingly based on column type if isinstance(c, MultipleSelectColumn): col_name = c.model_attribute_name # Add widget to MapperMixin collection self.addMapping( col_name, self.column_widget, c.mandatory, pseudoname=c.ui_display() ) # Bump up row_id row_id += 1 self.entity_scroll_area.setWidget(self.scroll_widget_contents) if self.entity_tab_widget is None: self.entity_tab_widget = QTabWidget(self) # Check if there are children and add foreign key browsers # Add primary tab if necessary self._add_primary_attr_widget() if not self._disable_collections: ch_entities = self.children_entities() for col, ch in ch_entities.iteritems(): if hasattr(col.entity_relation, 'show_in_parent'): if col.entity_relation.show_in_parent != '0': self._add_fk_browser(ch, col) else: self._add_fk_browser(ch, col) #Add tab widget if entity supports documents if self._entity.supports_documents: self.doc_widget = SupportingDocumentsWidget( self._entity.supporting_doc, self._ent_document_model, self ) # Map the source document manager object self.addMapping( 'documents', self.doc_widget.source_document_manager ) # # # Add attribute tab # self._add_primary_attr_widget() # Add supporting documents tab self.entity_tab_widget.addTab( self.doc_widget, self.tr('Supporting Documents') ) # Return the correct widget if not self.entity_tab_widget is None: return self.entity_tab_widget return self.entity_scroll_area def _add_primary_attr_widget(self): # Check if the primary entity # exists and add if it does not pr_txt = self.tr('Primary') if not self.entity_tab_widget is None: tab_txt = self.entity_tab_widget.tabText(0) if not tab_txt == pr_txt: self.entity_tab_widget.addTab( self.entity_scroll_area, pr_txt ) def _add_fk_browser(self, child_entity, column): # Create and add foreign key # browser to the collection from stdm.ui.entity_browser import ( EntityBrowserWithEditor ) attr = u'{0}_collection'.format(child_entity.name) # Return if the attribute does not exist if not hasattr(self._model, attr): return entity_browser = EntityBrowserWithEditor( child_entity, self, MANAGE, False, plugin=self.plugin ) entity_browser.buttonBox.setVisible(False) entity_browser.record_filter = [] if len(child_entity.label) > 2: column_label = child_entity.label else: # Split and join to filter out entity name prefix # e.g. 'lo_parcel' to 'parcel' column_label = format_name(" ".join(child_entity.name.split("_", 1)[1:])) self.entity_tab_widget.addTab( entity_browser, u'{0}'.format( column_label ) ) self.set_filter(child_entity, entity_browser) def set_filter(self, entity, browser): col = self.filter_col(entity) child_model = entity_model(entity) child_model_obj = child_model() col_obj = getattr(child_model, col.name) if self.model() is not None: if self.model().id is None: browser.filtered_records = [] else: browser.filtered_records = child_model_obj.queryObject().filter( col_obj == self.model().id ).all() if self.edit_model is not None: browser.filtered_records = child_model_obj.queryObject().filter( col_obj == self.edit_model.id ).all() if self.edit_model is None and self.model() is None: browser.filtered_records = [] def filter_col(self, child_entity): for col in child_entity.columns.values(): if col.TYPE_INFO == 'FOREIGN_KEY': parent_entity = col.parent if parent_entity == self._entity: return col def children_entities(self): """ :return: Returns a list of children entities that refer to the main entity as the parent. :rtype: OrderedDict """ child_columns = OrderedDict() for ch in self._entity.children(): if ch.TYPE_INFO == Entity.TYPE_INFO: for col in ch.columns.values(): if hasattr(col, 'entity_relation'): if col.parent.name == self._entity.name: child_columns[col] = ch return child_columns def document_widget(self): """ :return: Returns the widget for managing the supporting documents for an entity if enabled. :rtype: SupportingDocumentsWidget """ return self.doc_widget def source_document_manager(self): """ :return: Returns an instance of the SourceDocumentManager only if supporting documents are enabled for the given entity. Otherwise, None if supporting documents are not enabled. :rtype: SourceDocumentManager """ if self.doc_widget is None: return None return self.doc_widget.source_document_manager def _highlight_asterisk(self, text): # Highlight asterisk in red c = '*' # Do not format if there is no asterisk if text.find(c) == -1: return text asterisk_highlight = '<span style=\" color:#ff0000;\">*</span>' text = text.replace(c, asterisk_highlight) return u'<html><head/><body><p>{0}</p></body></html>'.format(text) def _custom_validate(self): """ Override of the MapperMixin which enables custom editor extensions to inject additional validation before saving form data. :return: Return True if the validation was successful, otherwise False. :rtype: bool """ if not self._editor_ext is None: return self._editor_ext.validate() # Return True if there is no custom editor extension specified return True def _post_save(self, model): """ Include additional post-save logic by custom extensions. :param model: SQLAlchemy model :type model: object """ if not self._editor_ext is None: self._editor_ext.post_save(model) def _get_entity_editor_widgets(self): """ Gets entity editor widgets and appends them to a dictionary """ if self.entity_tab_widget: tab_count = self.entity_tab_widget.count() for i in range(tab_count): tab_object = self.entity_tab_widget.widget(i) tab_text = self.entity_tab_widget.tabText(i) self.entity_editor_widgets[tab_text] = tab_object else: self.entity_editor_widgets['no_tab'] = self.entity_scroll_area
class EntityEditorDialog(QDialog, MapperMixin): """ Dialog for editing entity attributes. """ addedModel = pyqtSignal(object) def __init__(self, entity, model=None, parent=None, manage_documents=True, collect_model=False, parent_entity=None, exclude_columns=[], plugin=None): """ Class constructor. :param entity: Entity object corresponding to a table object. :type entity: Entity :param model: Data object for loading data into the form widgets. If the model is set, then the editor dialog is assumed to be in edit mode. :type model: object :param parent: Parent widget that the form belongs to. :type parent: QWidget :param manage_documents: True if the dialog should provide controls for managing supporting documents. Only applicable if the entity allows for supporting documents to be attached. :type manage_documents: bool :param collect_model: If set to True only returns the filled form model without saving it to the database. :type collect_model: Boolean :param parent_entity: The parent entity of the editor :type parent_entity: Object :param exclude_columns: List of columns to be excluded if in a list. :type exclude_columns: List :return: If collect_model, returns SQLAlchemy Model """ QDialog.__init__(self, parent) self.entity_table_model = {} self.collection_suffix = self.tr('Collection') #Set minimum width self.setMinimumWidth(450) self.plugin = plugin #Flag for mandatory columns self.has_mandatory = False self.reload_form = False self._entity = entity self.edit_model = model self.column_widgets = OrderedDict() self._parent = parent self.exclude_columns = exclude_columns self.entity_tab_widget = None self._disable_collections = False self.filter_val = None self.parent_entity = parent_entity self.child_models = OrderedDict() self.entity_scroll_area = None self.entity_editor_widgets = OrderedDict() # Set notification layout bar self.vlNotification = QVBoxLayout() self.vlNotification.setObjectName('vlNotification') self._notifBar = NotificationBar(self.vlNotification) self.do_not_check_dirty = False # Set manage documents only if the entity supports documents if self._entity.supports_documents: self._manage_documents = manage_documents else: self._manage_documents = False # Setup entity model self._ent_document_model = None if self._entity.supports_documents: self.ent_model, self._ent_document_model = entity_model( self._entity, with_supporting_document=True) else: self.ent_model = entity_model(self._entity) if not model is None: self.ent_model = model MapperMixin.__init__(self, self.ent_model, entity) self.collect_model = collect_model self.register_column_widgets() try: if isinstance(parent._parent, EntityEditorDialog): # hide collections form child editor self._disable_collections = True except AttributeError: self._parent._parent = None # Set title editor_trans = self.tr('Editor') if self._entity.label is not None: if self._entity.label != '': title_str = self._entity.label else: title_str = format_name(self._entity.short_name) else: title_str = format_name(self._entity.short_name) self.title = u'{0} {1}'.format(title_str, editor_trans) self.setWindowTitle(self.title) self._init_gui() self.adjustSize() self._get_entity_editor_widgets() if isinstance(parent._parent, EntityEditorDialog): self.parent_entity = parent.parent_entity self.set_parent_values() # make the size smaller to differentiate from parent and as it # only has few tabs. self.adjustSize() self.attribute_mappers = self._attr_mapper_collection # Exception title for editor extension exceptions self._ext_exc_msg = self.tr( 'An error has occured while executing Python code in the editor ' 'extension:') # Register custom editor extension if specified self._editor_ext = entity_dlg_extension(self) if not self._editor_ext is None: self._editor_ext.post_init() # Initialize CascadingFieldContext objects self._editor_ext.connect_cf_contexts() def _init_gui(self): # Setup base elements self.gridLayout = QGridLayout(self) self.gridLayout.setObjectName('glMain') self.gridLayout.addLayout(self.vlNotification, 0, 0, 1, 1) QApplication.processEvents() #set widgets values column_widget_area = self._setup_columns_content_area() self.gridLayout.addWidget(column_widget_area, 1, 0, 1, 1) QApplication.processEvents() # Add notification for mandatory columns if applicable next_row = 2 if self.has_mandatory: self.required_fields_lbl = QLabel(self) msg = self.tr('Please fill out all required (*) fields.') msg = self._highlight_asterisk(msg) self.required_fields_lbl.setText(msg) self.gridLayout.addWidget(self.required_fields_lbl, next_row, 0, 1, 2) # Bump up row reference next_row += 1 self.buttonBox = QDialogButtonBox(self) self.buttonBox.setObjectName('buttonBox') self.gridLayout.addWidget(self.buttonBox, next_row, 0, 1, 1) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Save) if self.edit_model is None: if not self.collect_model: self.save_new_button = QPushButton( QApplication.translate('EntityEditorDialog', 'Save and New')) self.buttonBox.addButton(self.save_new_button, QDialogButtonBox.ActionRole) # edit model, collect model # adding new record for child # Saving in parent editor if not isinstance(self._parent._parent, EntityEditorDialog): # adding a new record if self.edit_model is None: # saving when digitizing. if self.collect_model: self.buttonBox.accepted.connect(self.on_model_added) # saving parent editor else: self.buttonBox.accepted.connect(self.save_parent_editor) self.save_new_button.clicked.connect(self.save_and_new) # updating existing record else: if not self.collect_model: # updating existing record of the parent editor self.buttonBox.accepted.connect(self.save_parent_editor) else: self.buttonBox.accepted.connect(self.on_model_added) # Saving in child editor else: # save and new record if self.edit_model is None: self.buttonBox.accepted.connect(self.on_child_saved) self.save_new_button.clicked.connect( lambda: self.on_child_saved(True)) else: # When updating an existing child editor save to the db self.buttonBox.accepted.connect(self.on_child_saved) #self.buttonBox.accepted.connect(self.submit) self.buttonBox.rejected.connect(self.cancel) @property def notification_bar(self): """ :return: Returns the dialog's notification bar. :rtype: NotificationBar """ return self._notifBar def save_parent_editor(self): """ Saves the parent editor and its children. """ self.submit() self.save_children() def set_parent_values(self): """ Sets the parent display column for the child. """ if self.parent_entity is None: return for col in self._entity.columns.values(): if col.TYPE_INFO == 'FOREIGN_KEY': parent_entity = col.parent if parent_entity == self.parent_entity: self.parent_widgets_value_setter(self._parent._parent, col) def parent_widgets_value_setter(self, parent, col): """ Finds and sets the value from parent widget and set it to the column widget of a child using the child column. :param parent: The parent widget :type parent: QWidget :param col: The child column object :type col: Object """ for parent_col, parent_widget in parent.column_widgets.iteritems(): if parent_col.name == col.name: self.single_parent_value_setter(col, parent_widget) break if parent_col.name in col.entity_relation.display_cols: self.single_parent_value_setter(col, parent_widget) break def single_parent_value_setter(self, col, parent_widget): """ Gets value from parent widget and set it to the column widget of a child using the child column. :param parent: The parent widget :type parent: QWidget :param col: The child column object :type col: Object """ local_widget = self.column_widgets[col] local_widget.show_clear_button() self.filter_val = parent_widget.text() local_widget.setText(self.filter_val) def save_and_new(self): """ A slot raised when Save and New button is click. It saves the form without showing a success message. Then it sets reload_form property to True so that entity_browser can re-load the form. """ from stdm.ui.entity_browser import (EntityBrowserWithEditor) self.submit(False, True) self.save_children() if self.is_valid: self.addedModel.emit(self.model()) self.setModel(self.ent_model()) self.clear() self.child_models.clear() for index in range(0, self.entity_tab_widget.count() - 1): if isinstance(self.entity_tab_widget.widget(index), EntityBrowserWithEditor): child_browser = self.entity_tab_widget.widget(index) child_browser.remove_rows() def on_model_added(self): """ A slot raised when a form is submitted with collect model set to True. There will be no success message and the form does not close. """ self.submit(True) self.addedModel.emit(self.model()) def closeEvent(self, event): ''' Raised when a request to close the window is received. Check the dirty state of input controls and prompt user to save if dirty. ''' if self.do_not_check_dirty: event.accept() return isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: # We need to ignore the event so that validation and # saving operations can be executed event.ignore() self.submit() elif userResponse == QMessageBox.No: event.accept() elif userResponse == QMessageBox.Cancel: event.ignore() else: event.accept() def on_child_saved(self, save_and_new=False): """ A slot raised when the save or save and new button is clicked. It sets the child_models dictionary of the parent when saved. :param save_and_new: A boolean indicating the save and new button is clicked to trigger the slot. :type save_and_new: Boolean """ if self.parent_entity is None: return self.submit(True) insert_pos = self._parent.tbEntity.model().rowCount() + 1 # Save to parent editor so that it is persistent. self._parent._parent.child_models[insert_pos, self._entity] = \ self.model() self.addedModel.emit(self.model()) if not save_and_new: self.accept() else: if self.is_valid: #self.addedModel.emit(self.model()) self.setModel(self.ent_model()) self.clear() self.set_parent_values() def save_children(self): """ Saves children models into the database by assigning the the id of the parent for foreign key column. """ if len(self.child_models) < 1: return children_obj = [] for row_entity, model in self.child_models.iteritems(): row_pos = row_entity[0] entity = row_entity[1] ent_model = entity_model(entity) entity_obj = ent_model() for col in entity.columns.values(): if col.TYPE_INFO == 'FOREIGN_KEY': if col.parent.name == self._entity.name: setattr(model, col.name, self.model().id) children_obj.append(model) entity_obj.saveMany(children_obj) def register_column_widgets(self): """ Registers the column widgets. """ # Append column labels and widgets table_name = self._entity.name columns = table_column_names(table_name) self.scroll_widget_contents = QWidget() self.scroll_widget_contents.setObjectName('scrollAreaWidgetContents') for c in self._entity.columns.values(): if c.name in self.exclude_columns: continue if not c.name in columns and not isinstance(c, VirtualColumn): continue # Get widget factory column_widget = ColumnWidgetRegistry.create( c, self.scroll_widget_contents, host=self) self.column_widgets[c] = column_widget def _setup_columns_content_area(self): # Only use this if entity supports documents # self.entity_tab_widget = None self.doc_widget = None self.entity_scroll_area = QScrollArea(self) self.entity_scroll_area.setFrameShape(QFrame.NoFrame) self.entity_scroll_area.setWidgetResizable(True) self.entity_scroll_area.setObjectName('scrollArea') # Grid layout for controls self.gl = QGridLayout(self.scroll_widget_contents) self.gl.setObjectName('gl_widget_contents') # Append column labels and widgets table_name = self._entity.name columns = table_column_names(table_name) # Iterate entity column and assert if they exist row_id = 0 for c, column_widget in self.column_widgets.iteritems(): if c.name in self.exclude_columns: continue if not c.name in columns and not isinstance(c, VirtualColumn): continue if column_widget is not None: header = c.ui_display() self.c_label = QLabel(self.scroll_widget_contents) # Format label text if it is a mandatory field if c.mandatory: header = u'{0} *'.format(c.ui_display()) #Highlight asterisk header = self._highlight_asterisk(header) self.c_label.setText(header) self.gl.addWidget(self.c_label, row_id, 0, 1, 1) self.column_widget = column_widget self.gl.addWidget(self.column_widget, row_id, 1, 1, 1) #Add user tip if specified for the column configuration if c.user_tip: self.tip_lbl = UserTipLabel(user_tip=c.user_tip) self.gl.addWidget(self.tip_lbl, row_id, 2, 1, 1) if c.mandatory and not self.has_mandatory: self.has_mandatory = True col_name = c.name #Replace name accordingly based on column type if isinstance(c, MultipleSelectColumn): col_name = c.model_attribute_name # Add widget to MapperMixin collection self.addMapping(col_name, self.column_widget, c.mandatory, pseudoname=c.ui_display()) # Bump up row_id row_id += 1 self.entity_scroll_area.setWidget(self.scroll_widget_contents) if self.entity_tab_widget is None: self.entity_tab_widget = QTabWidget(self) # Check if there are children and add foreign key browsers # Add primary tab if necessary self._add_primary_attr_widget() if not self._disable_collections: ch_entities = self.children_entities() for col, ch in ch_entities.iteritems(): if hasattr(col.entity_relation, 'show_in_parent'): if col.entity_relation.show_in_parent != '0': self._add_fk_browser(ch, col) else: self._add_fk_browser(ch, col) #Add tab widget if entity supports documents if self._entity.supports_documents: self.doc_widget = SupportingDocumentsWidget( self._entity.supporting_doc, self._ent_document_model, self) # Map the source document manager object self.addMapping('documents', self.doc_widget.source_document_manager) # # # Add attribute tab # self._add_primary_attr_widget() # Add supporting documents tab self.entity_tab_widget.addTab(self.doc_widget, self.tr('Supporting Documents')) # Return the correct widget if not self.entity_tab_widget is None: return self.entity_tab_widget return self.entity_scroll_area def _add_primary_attr_widget(self): # Check if the primary entity # exists and add if it does not pr_txt = self.tr('Primary') if not self.entity_tab_widget is None: tab_txt = self.entity_tab_widget.tabText(0) if not tab_txt == pr_txt: self.entity_tab_widget.addTab(self.entity_scroll_area, pr_txt) def _add_fk_browser(self, child_entity, column): # Create and add foreign key # browser to the collection from stdm.ui.entity_browser import (EntityBrowserWithEditor) attr = u'{0}_collection'.format(child_entity.name) # Return if the attribute does not exist if not hasattr(self._model, attr): return entity_browser = EntityBrowserWithEditor(child_entity, self, MANAGE, False, plugin=self.plugin) entity_browser.buttonBox.setVisible(False) entity_browser.record_filter = [] if len(child_entity.label) > 2: column_label = child_entity.label else: # Split and join to filter out entity name prefix # e.g. 'lo_parcel' to 'parcel' column_label = format_name(" ".join( child_entity.name.split("_", 1)[1:])) self.entity_tab_widget.addTab(entity_browser, u'{0}'.format(column_label)) self.set_filter(child_entity, entity_browser) def set_filter(self, entity, browser): col = self.filter_col(entity) child_model = entity_model(entity) child_model_obj = child_model() col_obj = getattr(child_model, col.name) if self.model() is not None: if self.model().id is None: browser.filtered_records = [] else: browser.filtered_records = child_model_obj.queryObject( ).filter(col_obj == self.model().id).all() if self.edit_model is not None: browser.filtered_records = child_model_obj.queryObject().filter( col_obj == self.edit_model.id).all() if self.edit_model is None and self.model() is None: browser.filtered_records = [] def filter_col(self, child_entity): for col in child_entity.columns.values(): if col.TYPE_INFO == 'FOREIGN_KEY': parent_entity = col.parent if parent_entity == self._entity: return col def children_entities(self): """ :return: Returns a list of children entities that refer to the main entity as the parent. :rtype: OrderedDict """ child_columns = OrderedDict() for ch in self._entity.children(): if ch.TYPE_INFO == Entity.TYPE_INFO: for col in ch.columns.values(): if hasattr(col, 'entity_relation'): if col.parent.name == self._entity.name: child_columns[col] = ch return child_columns def document_widget(self): """ :return: Returns the widget for managing the supporting documents for an entity if enabled. :rtype: SupportingDocumentsWidget """ return self.doc_widget def source_document_manager(self): """ :return: Returns an instance of the SourceDocumentManager only if supporting documents are enabled for the given entity. Otherwise, None if supporting documents are not enabled. :rtype: SourceDocumentManager """ if self.doc_widget is None: return None return self.doc_widget.source_document_manager def _highlight_asterisk(self, text): # Highlight asterisk in red c = '*' # Do not format if there is no asterisk if text.find(c) == -1: return text asterisk_highlight = '<span style=\" color:#ff0000;\">*</span>' text = text.replace(c, asterisk_highlight) return u'<html><head/><body><p>{0}</p></body></html>'.format(text) def _custom_validate(self): """ Override of the MapperMixin which enables custom editor extensions to inject additional validation before saving form data. :return: Return True if the validation was successful, otherwise False. :rtype: bool """ if not self._editor_ext is None: return self._editor_ext.validate() # Return True if there is no custom editor extension specified return True def _post_save(self, model): """ Include additional post-save logic by custom extensions. :param model: SQLAlchemy model :type model: object """ if not self._editor_ext is None: self._editor_ext.post_save(model) def _get_entity_editor_widgets(self): """ Gets entity editor widgets and appends them to a dictionary """ if self.entity_tab_widget: tab_count = self.entity_tab_widget.count() for i in range(tab_count): tab_object = self.entity_tab_widget.widget(i) tab_text = self.entity_tab_widget.tabText(i) self.entity_editor_widgets[tab_text] = tab_object else: self.entity_editor_widgets['no_tab'] = self.entity_scroll_area
class DHakkinda(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.resize(500, 350) self.setMaximumSize(500, 350) self.gLayout = QGridLayout(self) self.logo = QLabel(self) self.logo.setPixmap(QPixmap(":/logo/data/logo.png")) self.gLayout.addWidget(self.logo, 0, 0, 2, 1) self.appName = QLabel(self) font = QFont() font.setPointSize(32) font.setWeight(50) self.appName.setFont(font) self.gLayout.addWidget(self.appName, 0, 1, 1, 2) self.appVersion = QLabel(self) font = QFont() font.setPointSize(9) font.setWeight(75) font.setBold(True) self.appVersion.setFont(font) self.appVersion.setAlignment(Qt.AlignHCenter | Qt.AlignTop) self.gLayout.addWidget(self.appVersion, 1, 1, 1, 2) self.gBox = QGroupBox(self) font = QFont() font.setPointSize(12) font.setWeight(75) font.setBold(True) self.gBox.setFont(font) self.gLayout2 = QGridLayout(self.gBox) self.scrollArea = QScrollArea(self.gBox) self.scrollArea.setFrameShape(QFrame.NoFrame) self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents.setGeometry(0, 0, 476, 199) self.gLayout3 = QGridLayout(self.scrollAreaWidgetContents) self.appHakkinda = QLabel(self.scrollAreaWidgetContents) font = QFont() font.setPointSize(9) font.setWeight(50) font.setBold(False) self.appHakkinda.setFont(font) self.appHakkinda.setWordWrap(True) self.gLayout3.addWidget(self.appHakkinda, 0, 0, 1, 1) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.gLayout2.addWidget(self.scrollArea, 0, 0, 1, 1) self.gLayout.addWidget(self.gBox, 2, 0, 2, 4) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.gLayout.addItem(spacerItem, 0, 3, 1, 1) spacerItem1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.gLayout.addItem(spacerItem1, 2, 1, 1, 2) self.setWindowTitle(u"Virux Hakkında") self.appName.setText(u"Virux") self.appVersion.setText(u"Sürüm %s" % QApplication.applicationVersion()) self.gBox.setTitle(u"Hakkında") self.appHakkinda.setText(u""" <p>Virux, platform bağımsız bir antivirüs yazılımıdır :P</p> <p>Yazılımıın bir arayüzü yoktur. Sadece sistem çubuğunda bir tepsi oluşur. Bu tepsi animasyon şeklindedir.</p> <p>Rasgele zamanlarda mevcut olan dialoglardan bir tanesi ekranda gözükecektir. Sadece eğlence amacıyla yapılmıştır...</p> <p><b>Geliştirici:</b> Metehan Özbek - <a href='mailto:[email protected]'>[email protected]</a></p> <p><b>Görsel Çalışma:</b> Yasin Özcan - <a href='mailto:[email protected]'>[email protected]</a></p> <p><b>Katkı Yapanlar:</b> Yaşar Arabacı - <a href='mailto:[email protected]'>[email protected]</a></p> <p><b>Lisans:</b> GPL v3</p> <p></p>""")
class XPopupWidget(QWidget): """ """ Direction = enum('North', 'South', 'East', 'West') Mode = enum('Popup', 'Dialog', 'ToolTip') Anchor = enum('TopLeft', 'TopCenter', 'TopRight', 'LeftTop', 'LeftCenter', 'LeftBottom', 'RightTop', 'RightCenter', 'RightBottom', 'BottomLeft', 'BottomCenter', 'BottomRight') aboutToShow = qt.Signal() accepted = qt.Signal() closed = qt.Signal() rejected = qt.Signal() resetRequested = qt.Signal() shown = qt.Signal() buttonClicked = qt.Signal(QAbstractButton) def __init__(self, parent=None, buttons=None): super(XPopupWidget, self).__init__(parent) # define custom properties self._anchor = XPopupWidget.Anchor.TopCenter self._autoCalculateAnchor = False self._autoCloseOnAccept = True self._autoCloseOnReject = True self._autoCloseOnFocusOut = False self._autoDefault = True self._first = True self._animated = False self._currentMode = None self._positionLinkedTo = [] # define controls self._resizable = True self._popupPadding = 10 self._titleBarVisible = True self._buttonBoxVisible = True self._dialogButton = QToolButton(self) self._closeButton = QToolButton(self) self._scrollArea = QScrollArea(self) self._sizeGrip = QSizeGrip(self) self._sizeGrip.setFixedWidth(12) self._sizeGrip.setFixedHeight(12) self._leftSizeGrip = QSizeGrip(self) self._leftSizeGrip.setFixedWidth(12) self._leftSizeGrip.setFixedHeight(12) if buttons is None: buttons = QDialogButtonBox.NoButton self._buttonBox = QDialogButtonBox(buttons, Qt.Horizontal, self) self._buttonBox.setContentsMargins(3, 0, 3, 9) self._scrollArea.setWidgetResizable(True) self._scrollArea.setFrameShape(QScrollArea.NoFrame) self._scrollArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) palette = self.palette() self._scrollArea.setPalette(palette) self._dialogButton.setToolTip('Popout to Dialog') self._closeButton.setToolTip('Close Popup') for btn in (self._dialogButton, self._closeButton): btn.setAutoRaise(True) btn.setIconSize(QSize(14, 14)) btn.setMaximumSize(16, 16) # setup the icons icon = QIcon(projexui.resources.find('img/dialog.png')) self._dialogButton.setIcon(icon) icon = QIcon(projexui.resources.find('img/close.png')) self._closeButton.setIcon(icon) # define the ui hlayout = QHBoxLayout() hlayout.setSpacing(0) hlayout.addStretch(1) hlayout.addWidget(self._dialogButton) hlayout.addWidget(self._closeButton) hlayout.setContentsMargins(0, 0, 0, 0) hlayout2 = QHBoxLayout() hlayout2.addWidget(self._buttonBox) hlayout2.setContentsMargins(0, 0, 3, 0) vlayout = QVBoxLayout() vlayout.addLayout(hlayout) vlayout.addWidget(self._scrollArea) vlayout.addLayout(hlayout2) vlayout.setContentsMargins(3, 2, 3, 2) vlayout.setSpacing(0) self.setLayout(vlayout) self.setPositionLinkedTo(parent) # set default properties self.setAutoFillBackground(True) self.setBackgroundRole(QPalette.Button) self.setWindowTitle('Popup') self.setFocusPolicy(Qt.StrongFocus) self.setCurrentMode(XPopupWidget.Mode.Popup) # create connections self._dialogButton.clicked.connect(self.setDialogMode) self._closeButton.clicked.connect(self.reject) self._buttonBox.accepted.connect(self.accept) self._buttonBox.rejected.connect(self.reject) self._buttonBox.clicked.connect(self.handleButtonClick) def addButton(self, button, role=QDialogButtonBox.ActionRole): """ Adds a custom button to the button box for this popup widget. :param button | <QAbstractButton> || <str> :return <button> || None (based on if a button or string was given) """ return self._buttonBox.addButton(button, role) def adjustContentsMargins( self ): """ Adjusts the contents for this widget based on the anchor and \ mode. """ anchor = self.anchor() mode = self.currentMode() # margins for a dialog if ( mode == XPopupWidget.Mode.Dialog ): self.setContentsMargins(0, 0, 0, 0) # margins for a top anchor point elif ( anchor & (XPopupWidget.Anchor.TopLeft | XPopupWidget.Anchor.TopCenter | XPopupWidget.Anchor.TopRight) ): self.setContentsMargins(0, self.popupPadding() + 5, 0, 0) # margins for a bottom anchor point elif ( anchor & (XPopupWidget.Anchor.BottomLeft | XPopupWidget.Anchor.BottomCenter | XPopupWidget.Anchor.BottomRight) ): self.setContentsMargins(0, 0, 0, self.popupPadding()) # margins for a left anchor point elif ( anchor & (XPopupWidget.Anchor.LeftTop | XPopupWidget.Anchor.LeftCenter | XPopupWidget.Anchor.LeftBottom) ): self.setContentsMargins(self.popupPadding(), 0, 0, 0) # margins for a right anchor point else: self.setContentsMargins(0, 0, self.popupPadding(), 0) self.adjustMask() def adjustMask(self): """ Updates the alpha mask for this popup widget. """ if self.currentMode() == XPopupWidget.Mode.Dialog: self.clearMask() return path = self.borderPath() bitmap = QBitmap(self.width(), self.height()) bitmap.fill(QColor('white')) painter = QPainter() painter.begin(bitmap) painter.setRenderHint(QPainter.Antialiasing) pen = QPen(QColor('black')) pen.setWidthF(0.75) painter.setPen(pen) painter.setBrush(QColor('black')) painter.drawPath(path) painter.end() self.setMask(bitmap) def adjustSize(self): """ Adjusts the size of this popup to best fit the new widget size. """ widget = self.centralWidget() if widget is None: super(XPopupWidget, self).adjustSize() return widget.adjustSize() hint = widget.minimumSizeHint() size = widget.minimumSize() width = max(size.width(), hint.width()) height = max(size.height(), hint.height()) width += 20 height += 20 if self._buttonBoxVisible: height += self.buttonBox().height() + 10 if self._titleBarVisible: height += max(self._dialogButton.height(), self._closeButton.height()) + 10 curr_w = self.width() curr_h = self.height() # determine if we need to move based on our anchor anchor = self.anchor() if anchor & (self.Anchor.LeftBottom | self.Anchor.RightBottom | \ self.Anchor.BottomLeft | self.Anchor.BottomCenter | \ self.Anchor.BottomRight): delta_y = height - curr_h elif anchor & (self.Anchor.LeftCenter | self.Anchor.RightCenter): delta_y = (height - curr_h) / 2 else: delta_y = 0 if anchor & (self.Anchor.RightTop | self.Anchor.RightCenter | \ self.Anchor.RightTop | self.Anchor.TopRight): delta_x = width - curr_w elif anchor & (self.Anchor.TopCenter | self.Anchor.BottomCenter): delta_x = (width - curr_w) / 2 else: delta_x = 0 self.setMinimumSize(width, height) self.resize(width, height) pos = self.pos() pos.setX(pos.x() - delta_x) pos.setY(pos.y() - delta_y) self.move(pos) @qt.Slot() def accept(self): """ Emits the accepted signal and closes the popup. """ if not self.signalsBlocked(): self.accepted.emit() if self.autoCloseOnAccept(): self.close() def anchor( self ): """ Returns the anchor point for this popup widget. :return <XPopupWidget.Anchor> """ return self._anchor def autoCalculateAnchor( self ): """ Returns whether or not this popup should calculate the anchor point on popup based on the parent widget and the popup point. :return <bool> """ return self._autoCalculateAnchor def autoCloseOnAccept( self ): """ Returns whether or not this popup widget manages its own close on accept behavior. :return <bool> """ return self._autoCloseOnAccept def autoCloseOnReject( self ): """ Returns whether or not this popup widget manages its own close on reject behavior. :return <bool> """ return self._autoCloseOnReject def autoCloseOnFocusOut(self): """ Returns whether or not this popup widget should auto-close when the user clicks off the view. :return <bool> """ return self._autoCloseOnFocusOut def autoDefault(self): """ Returns whether or not clicking enter should default to the accept key. :return <bool> """ return self._autoDefault def borderPath(self): """ Returns the border path that will be drawn for this widget. :return <QPainterPath> """ path = QPainterPath() x = 1 y = 1 w = self.width() - 2 h = self.height() - 2 pad = self.popupPadding() anchor = self.anchor() # create a path for a top-center based popup if anchor == XPopupWidget.Anchor.TopCenter: path.moveTo(x, y + pad) path.lineTo(x + ((w/2) - pad), y + pad) path.lineTo(x + (w/2), y) path.lineTo(x + ((w/2) + pad), y + pad) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a top-left based popup elif anchor == XPopupWidget.Anchor.TopLeft: path.moveTo(x, y + pad) path.lineTo(x + pad, y) path.lineTo(x + 2 * pad, y + pad) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a top-right based popup elif anchor == XPopupWidget.Anchor.TopRight: path.moveTo(x, y + pad) path.lineTo(x + w - 2 * pad, y + pad) path.lineTo(x + w - pad, y) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a bottom-left based popup elif anchor == XPopupWidget.Anchor.BottomLeft: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + 2 * pad, y + h - pad) path.lineTo(x + pad, y + h) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a south based popup elif anchor == XPopupWidget.Anchor.BottomCenter: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + ((w/2) + pad), y + h - pad) path.lineTo(x + (w/2), y + h) path.lineTo(x + ((w/2) - pad), y + h - pad) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a bottom-right based popup elif anchor == XPopupWidget.Anchor.BottomRight: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + w - pad, y + h) path.lineTo(x + w - 2 * pad, y + h - pad) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a right-top based popup elif anchor == XPopupWidget.Anchor.RightTop: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w, y + pad) path.lineTo(x + w - pad, y + 2 * pad) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a right-center based popup elif anchor == XPopupWidget.Anchor.RightCenter: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w - pad, y + ((h/2) - pad)) path.lineTo(x + w, y + (h/2)) path.lineTo(x + w - pad, y + ((h/2) + pad)) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a right-bottom based popup elif anchor == XPopupWidget.Anchor.RightBottom: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w - pad, y + h - 2 * pad) path.lineTo(x + w, y + h - pad) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a left-top based popup elif anchor == XPopupWidget.Anchor.LeftTop: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x + pad, y + 2 * pad) path.lineTo(x, y + pad) path.lineTo(x + pad, y) # create a path for an left-center based popup elif anchor == XPopupWidget.Anchor.LeftCenter: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x + pad, y + ((h/2) + pad)) path.lineTo(x, y + (h/2)) path.lineTo(x + pad, y + ((h/2) - pad)) path.lineTo(x + pad, y) # create a path for a left-bottom based popup elif anchor == XPopupWidget.Anchor.LeftBottom: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x, y + h - pad) path.lineTo(x + pad, y + h - 2 * pad) path.lineTo(x + pad, y) return path def buttonBox( self ): """ Returns the button box that is used to control this popup widget. :return <QDialogButtonBox> """ return self._buttonBox def centralWidget( self ): """ Returns the central widget that is being used by this popup. :return <QWidget> """ return self._scrollArea.widget() def close(self): """ Closes the popup widget and central widget. """ widget = self.centralWidget() if widget and not widget.close(): return super(XPopupWidget, self).close() def closeEvent(self, event): widget = self.centralWidget() if widget and not widget.close() and \ self.currentMode() != XPopupWidget.Mode.ToolTip: event.ignore() else: super(XPopupWidget, self).closeEvent(event) self.closed.emit() def currentMode( self ): """ Returns the current mode for this widget. :return <XPopupWidget.Mode> """ return self._currentMode @deprecatedmethod('XPopupWidget', 'Direction is no longer used, use anchor instead') def direction( self ): """ Returns the current direction parameter for this widget. :return <XPopupWidget.Direction> """ anchor = self.anchor() if ( anchor & (XPopupWidget.Anchor.TopLeft | XPopupWidget.Anchor.TopCenter | XPopupWidget.Anchor.TopRight) ): return XPopupWidget.Direction.North elif ( anchor & (XPopupWidget.Anchor.BottomLeft | XPopupWidget.Anchor.BottomCenter | XPopupWidget.Anchor.BottomRight) ): return XPopupWidget.Direction.South elif ( anchor & (XPopupWidget.Anchor.LeftTop | XPopupWidget.Anchor.LeftCenter | XPopupWidget.Anchor.LeftBottom) ): return XPopupWidget.Direction.East else: return XPopupWidget.Direction.West def eventFilter(self, object, event): """ Processes when the window is moving to update the position for the popup if in popup mode. :param object | <QObject> event | <QEvent> """ links = self.positionLinkedTo() is_dialog = self.currentMode() == self.Mode.Dialog if object not in links: return False if event.type() == event.Close: self.close() return False if event.type() == event.Hide and not is_dialog: self.hide() return False if event.type() == event.Move and not is_dialog: deltaPos = event.pos() - event.oldPos() self.move(self.pos() + deltaPos) return False if self.currentMode() != self.Mode.ToolTip: return False if event.type() == event.Leave: pos = object.mapFromGlobal(QCursor.pos()) if (not object.rect().contains(pos)): self.close() event.accept() return True if event.type() in (event.MouseButtonPress, event.MouseButtonDblClick): self.close() event.accept() return True return False @qt.Slot(QAbstractButton) def handleButtonClick(self, button): """ Handles the button click for this widget. If the Reset button was clicked, then the resetRequested signal will be emitted. All buttons will emit the buttonClicked signal. :param button | <QAbstractButton> """ if ( self.signalsBlocked() ): return if ( button == self._buttonBox.button(QDialogButtonBox.Reset) ): self.resetRequested.emit() self.buttonClicked.emit(button) def isAnimated(self): """ Returns whether or not the popup widget should animate its opacity when it is shown. :return <bool> """ return self._animated def isResizable(self): """ Returns if this popup is resizable or not. :return <bool> """ return self._resizable def keyPressEvent( self, event ): """ Looks for the Esc key to close the popup. :param event | <QKeyEvent> """ if ( event.key() == Qt.Key_Escape ): self.reject() event.accept() return elif ( event.key() in (Qt.Key_Return, Qt.Key_Enter) ): if self._autoDefault: self.accept() event.accept() return super(XPopupWidget, self).keyPressEvent(event) def mapAnchorFrom( self, widget, globalPos ): """ Returns the anchor point that best fits within the given widget from the inputed global position. :param widget | <QWidget> globalPos | <QPoint> :return <XPopupWidget.Anchor> """ localPos = widget.mapFromGlobal(globalPos) x = localPos.x() y = localPos.y() w = widget.width() h = widget.height() cw = self.width() / 2 ch = self.height() / 2 # by default, try to do a center point, so make sure the center point # is at least 1/2 the width longer from each edge if x < cw and h - y < ch: return XPopupWidget.Anchor.BottomLeft elif x < cw: return XPopupWidget.Anchor.TopLeft elif w - x < cw and h - y < ch: return XPopupWidget.Anchor.BottomRight elif w - x < cw: return XPopupWidget.Anchor.TopRight elif h - y < ch: return XPopupWidget.Anchor.BottomCenter else: return XPopupWidget.Anchor.TopCenter def popup(self, pos=None): """ Pops up this widget at the inputed position. The inputed point should \ be in global space. :param pos | <QPoint> :return <bool> success """ if self._first and self.centralWidget() is not None: self.adjustSize() self._first = False if not self.signalsBlocked(): self.aboutToShow.emit() if not pos: pos = QCursor.pos() if self.currentMode() == XPopupWidget.Mode.Dialog and \ self.isVisible(): return False elif self.currentMode() == XPopupWidget.Mode.Dialog: self.setPopupMode() # auto-calculate the point if self.autoCalculateAnchor(): self.setAnchor(self.mapAnchorFrom( self.parent(), pos )) pad = self.popupPadding() # determine where to move based on the anchor anchor = self.anchor() # MODIFY X POSITION # align x-left if ( anchor & (XPopupWidget.Anchor.TopLeft | XPopupWidget.Anchor.BottomLeft) ): pos.setX(pos.x() - pad) # align x-center elif ( anchor & (XPopupWidget.Anchor.TopCenter | XPopupWidget.Anchor.BottomCenter) ): pos.setX(pos.x() - self.width() / 2) # align x-right elif ( anchor & (XPopupWidget.Anchor.TopRight | XPopupWidget.Anchor.BottomRight) ): pos.setX(pos.x() - self.width() + pad) # align x-padded elif ( anchor & (XPopupWidget.Anchor.RightTop | XPopupWidget.Anchor.RightCenter | XPopupWidget.Anchor.RightBottom) ): pos.setX(pos.x() - self.width()) # MODIFY Y POSITION # align y-top if ( anchor & (XPopupWidget.Anchor.LeftTop | XPopupWidget.Anchor.RightTop) ): pos.setY(pos.y() - pad) # align y-center elif ( anchor & (XPopupWidget.Anchor.LeftCenter | XPopupWidget.Anchor.RightCenter) ): pos.setY(pos.y() - self.height() / 2) # align y-bottom elif ( anchor & (XPopupWidget.Anchor.LeftBottom | XPopupWidget.Anchor.RightBottom) ): pos.setY(pos.y() - self.height() + pad) # align y-padded elif ( anchor & (XPopupWidget.Anchor.BottomLeft | XPopupWidget.Anchor.BottomCenter | XPopupWidget.Anchor.BottomRight) ): pos.setY(pos.y() - self.height()) self.adjustMask() self.move(pos) self.update() self.setUpdatesEnabled(True) if self.isAnimated(): anim = QPropertyAnimation(self, 'windowOpacity') anim.setParent(self) anim.setStartValue(0.0) anim.setEndValue(self.windowOpacity()) anim.setDuration(500) anim.finished.connect(anim.deleteLater) self.setWindowOpacity(0.0) else: anim = None self.show() if self.currentMode() != XPopupWidget.Mode.ToolTip: self.activateWindow() widget = self.centralWidget() if widget: self.centralWidget().setFocus() if anim: anim.start() if not self.signalsBlocked(): self.shown.emit() return True def paintEvent(self, event): """ Overloads the paint event to handle painting pointers for the popup \ mode. :param event | <QPaintEvent> """ # use the base technique for the dialog mode if self.currentMode() == XPopupWidget.Mode.Dialog: super(XPopupWidget, self).paintEvent(event) return # setup the coloring options palette = self.palette() painter = QPainter() painter.begin(self) pen = QPen(palette.color(palette.Window).darker(130)) pen.setWidthF(1.75) painter.setPen(pen) painter.setRenderHint(painter.Antialiasing) painter.setBrush(palette.color(palette.Window)) painter.drawPath(self.borderPath()) painter.end() def popupPadding(self): """ Returns the amount of pixels to pad the popup arrow for this widget. :return <int> """ return self._popupPadding def positionLinkedTo(self): """ Returns the widget that this popup is linked to for positional changes. :return [<QWidget>, ..] """ return self._positionLinkedTo @qt.Slot() def reject(self): """ Emits the accepted signal and closes the popup. """ if not self.signalsBlocked(): self.rejected.emit() if self.autoCloseOnReject(): self.close() def resizeEvent(self, event): """ Resizes this widget and updates the mask. :param event | <QResizeEvent> """ self.setUpdatesEnabled(False) super(XPopupWidget, self).resizeEvent(event) self.adjustMask() self.setUpdatesEnabled(True) x = self.width() - self._sizeGrip.width() y = self.height() - self._sizeGrip.height() self._leftSizeGrip.move(0, y) self._sizeGrip.move(x, y) def scrollArea(self): """ Returns the scroll area widget for this popup. :return <QScrollArea> """ return self._scrollArea def setAnimated(self, state): """ Sets whether or not the popup widget should animate its opacity when it is shown. :param state | <bool> """ self._animated = state self.setAttribute(Qt.WA_TranslucentBackground, state) def setAutoCloseOnAccept( self, state ): """ Sets whether or not the popup handles closing for accepting states. :param state | <bool> """ self._autoCloseOnAccept = state def setAutoCloseOnReject( self, state ): """ Sets whether or not the popup handles closing for rejecting states. :param state | <bool> """ self._autoCloseOnReject = state def setAutoDefault(self, state): """ Sets whether or not the buttons should respond to defaulting options when the user is interacting with it. :param state | <bool> """ self._autoDefault = state for button in self.buttonBox().buttons(): button.setAutoDefault(state) button.setDefault(state) def setAnchor( self, anchor ): """ Sets the anchor position for this popup widget to the inputed point. :param anchor | <XPopupWidget.Anchor> """ self._anchor = anchor self.adjustContentsMargins() def setAutoCalculateAnchor( self, state ): """ Sets whether or not this widget should auto-calculate the anchor point based on the parent position when the popup is triggered. :param state | <bool> """ self._autoCalculateAnchor = state def setAutoCloseOnFocusOut(self, state): """ Sets whether or not this popup widget should auto-close when the user clicks off the view. :param state | <bool> """ self._autoCloseOnFocusOut = state self.updateModeSettings() def setCentralWidget( self, widget ): """ Sets the central widget that will be used by this popup. :param widget | <QWidget> || None """ self._scrollArea.takeWidget() self._scrollArea.setWidget(widget) self.adjustSize() def setCurrentMode( self, mode ): """ Sets the current mode for this dialog to the inputed mode. :param mode | <XPopupWidget.Mode> """ if ( self._currentMode == mode ): return self._currentMode = mode self.updateModeSettings() @qt.Slot() def setDialogMode(self): """ Sets the current mode value to Dialog. """ self.setCurrentMode(XPopupWidget.Mode.Dialog) @deprecatedmethod('XPopupWidget', 'Direction is no longer used, use setAnchor instead') def setDirection( self, direction ): """ Sets the direction for this widget to the inputed direction. :param direction | <XPopupWidget.Direction> """ if ( direction == XPopupWidget.Direction.North ): self.setAnchor(XPopupWidget.Anchor.TopCenter) elif ( direction == XPopupWidget.Direction.South ): self.setAnchor(XPopupWidget.Anchor.BottomCenter) elif ( direction == XPopupWidget.Direction.East ): self.setAnchor(XPopupWidget.Anchor.LeftCenter) else: self.setAnchor(XPopupWidget.Anchor.RightCenter) def setPalette(self, palette): """ Sets the palette for this widget and the scroll area. :param palette | <QPalette> """ super(XPopupWidget, self).setPalette(palette) self._scrollArea.setPalette(palette) def setPopupMode( self ): """ Sets the current mode value to Popup. """ self.setCurrentMode(XPopupWidget.Mode.Popup) def setPopupPadding( self, padding ): """ Sets the amount to pad the popup area when displaying this widget. :param padding | <int> """ self._popupPadding = padding self.adjustContentsMargins() def setPositionLinkedTo(self, widgets): """ Sets the widget that this popup will be linked to for positional changes. :param widgets | <QWidget> || [<QWidget>, ..] """ if type(widgets) in (list, set, tuple): new_widgets = list(widgets) else: new_widgets = [] widget = widgets while widget: widget.installEventFilter(self) new_widgets.append(widget) widget = widget.parent() self._positionLinkedTo = new_widgets def setResizable( self, state ): self._resizable = state self._sizeGrip.setVisible(state) self._leftSizeGrip.setVisible(state) def setShowButtonBox(self, state): self._buttonBoxVisible = state self.buttonBox().setVisible(state) def setShowTitleBar(self, state): self._titleBarVisible = state self._dialogButton.setVisible(state) self._closeButton.setVisible(state) def setToolTipMode(self): """ Sets the mode for this popup widget to ToolTip """ self.setCurrentMode(XPopupWidget.Mode.ToolTip) def setVisible(self, state): super(XPopupWidget, self).setVisible(state) widget = self.centralWidget() if widget: widget.setVisible(state) def timerEvent( self, event ): """ When the timer finishes, hide the tooltip popup widget. :param event | <QEvent> """ if self.currentMode() == XPopupWidget.Mode.ToolTip: self.killTimer(event.timerId()) event.accept() self.close() else: super(XPopupWidget, self).timerEvent(event) def updateModeSettings(self): mode = self.currentMode() is_visible = self.isVisible() # display as a floating dialog if mode == XPopupWidget.Mode.Dialog: self.setWindowFlags(Qt.Dialog | Qt.Tool) self.setAttribute(Qt.WA_TransparentForMouseEvents, False) self._closeButton.setVisible(False) self._dialogButton.setVisible(False) # display as a user tooltip elif mode == XPopupWidget.Mode.ToolTip: flags = Qt.Popup | Qt.FramelessWindowHint self.setWindowFlags(flags) self.setBackgroundRole(QPalette.Window) self.setAttribute(Qt.WA_TransparentForMouseEvents) self.setShowTitleBar(False) self.setShowButtonBox(False) self.setFocusPolicy(Qt.NoFocus) # hide the scrollbars policy = Qt.ScrollBarAlwaysOff self._scrollArea.setVerticalScrollBarPolicy(policy) self._scrollArea.setHorizontalScrollBarPolicy(policy) # display as a popup widget else: flags = Qt.Popup | Qt.FramelessWindowHint if not self.autoCloseOnFocusOut(): flags |= Qt.Tool self.setWindowFlags(flags) self._closeButton.setVisible(self._titleBarVisible) self._dialogButton.setVisible(self._titleBarVisible) self.setBackgroundRole(QPalette.Button) self.adjustContentsMargins() if ( is_visible ): self.show() @staticmethod @deprecatedmethod('XPopupWidget', 'This method no longer has an effect as we are not '\ 'storing references to the tooltip.') def hideToolTip(key = None): """ Hides any existing tooltip popup widgets. :warning This method is deprecated! """ pass @staticmethod def showToolTip( text, point = None, anchor = None, parent = None, background = None, foreground = None, key = None, seconds = 5 ): """ Displays a popup widget as a tooltip bubble. :param text | <str> point | <QPoint> || None anchor | <XPopupWidget.Mode.Anchor> || None parent | <QWidget> || None background | <QColor> || None foreground | <QColor> || None key | <str> || None seconds | <int> """ if point is None: point = QCursor.pos() if parent is None: parent = QApplication.activeWindow() if anchor is None and parent is None: anchor = XPopupWidget.Anchor.TopCenter # create a new tooltip widget widget = XPopupWidget(parent) widget.setToolTipMode() widget.setResizable(False) # create the tooltip label label = QLabel(text, widget) label.setOpenExternalLinks(True) label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) label.setMargin(3) label.setIndent(3) label.adjustSize() widget.setCentralWidget(label) # update the tip label.adjustSize() widget.adjustSize() palette = widget.palette() if not background: background = palette.color(palette.ToolTipBase) if not foreground: foreground = palette.color(palette.ToolTipText) palette.setColor(palette.Window, QColor(background)) palette.setColor(palette.WindowText, QColor(foreground)) widget.setPalette(palette) widget.centralWidget().setPalette(palette) if anchor is None: widget.setAutoCalculateAnchor(True) else: widget.setAnchor(anchor) widget.setAutoCloseOnFocusOut(True) widget.setAttribute(Qt.WA_DeleteOnClose) widget.popup(point) widget.startTimer(1000 * seconds) return widget
class XNavigationEdit(XLineEdit): """ """ navigationChanged = Signal() __designer_icon__ = projexui.resources.find('img/ui/navigate.png') def __init__( self, parent = None ): super(XNavigationEdit, self).__init__( parent ) # define custom properties self._separator = '/' self._partsEditingEnabled = True self._originalText = '' self._scrollWidget = QScrollArea(self) self._partsWidget = QWidget(self._scrollWidget) self._buttonGroup = QButtonGroup(self) self._scrollAmount = 0 self._navigationModel = None # create the completer tree palette = self.palette() palette.setColor(palette.Base, palette.color(palette.Window)) palette.setColor(palette.Text, palette.color(palette.WindowText)) bg = palette.color(palette.Highlight) abg = bg.darker(115) fg = palette.color(palette.HighlightedText) sbg = 'rgb(%s, %s, %s)' % (bg.red(), bg.green(), bg.blue()) sabg = 'rgb(%s, %s, %s)' % (abg.red(), abg.green(), abg.blue()) sfg = 'rgb(%s, %s, %s)' % (fg.red(), fg.green(), fg.blue()) style = 'QTreeView::item:hover { '\ ' color: %s;'\ ' background: qlineargradient(x1:0,'\ ' y1:0,'\ ' x2:0,'\ ' y2:1,'\ ' stop: 0 %s,'\ ' stop: 1 %s);'\ '}' % (sfg, sbg, sabg) self._completerTree = QTreeView(self) self._completerTree.setStyleSheet(style) self._completerTree.header().hide() self._completerTree.setFrameShape(QTreeView.Box) self._completerTree.setFrameShadow(QTreeView.Plain) self._completerTree.setPalette(palette) self._completerTree.setEditTriggers(QTreeView.NoEditTriggers) self._completerTree.setWindowFlags(Qt.Popup) self._completerTree.installEventFilter(self) self._completerTree.setRootIsDecorated(False) self._completerTree.setItemsExpandable(False) # create the editing widget layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addStretch() self._scrollWidget.setFrameShape( QScrollArea.NoFrame ) self._scrollWidget.setFocusPolicy(Qt.NoFocus) self._scrollWidget.setWidget(self._partsWidget) self._scrollWidget.setWidgetResizable(True) self._scrollWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._scrollWidget.setAlignment(Qt.AlignTop | Qt.AlignRight) self._scrollWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._scrollWidget.setContentsMargins(0, 0, 0, 0) self._scrollWidget.setViewportMargins(0, 0, 0, 0) self._scrollWidget.move(2, 2) self._partsWidget.setLayout(layout) self._partsWidget.setCursor(Qt.ArrowCursor) self._partsWidget.setAutoFillBackground(True) self._partsWidget.setFixedHeight(self.height() - 12) palette = self._partsWidget.palette() palette.setColor(palette.Background, palette.color(palette.Base)) self._partsWidget.setPalette(palette) # create connections self._completerTree.clicked.connect( self.navigateToIndex ) self._buttonGroup.buttonClicked.connect( self.handleButtonClick ) self._scrollWidget.horizontalScrollBar().valueChanged.connect( self.scrollParts ) def acceptEdit( self ): """ Accepts the current text and rebuilds the parts widget. """ if ( self._partsWidget.isVisible() ): return False use_completion = self.completer().popup().isVisible() completion = self.completer().currentCompletion() self._completerTree.hide() self.completer().popup().hide() if ( use_completion ): self.setText(completion) else: self.rebuild() return True def cancelEdit( self ): """ Rejects the current edit and shows the parts widget. """ if ( self._partsWidget.isVisible() ): return False self._completerTree.hide() self.completer().popup().hide() self.setText(self._originalText) return True def currentItem( self ): """ Returns the current navigation item from the current path. :return <XNavigationItem> || None """ model = self.navigationModel() if ( not model ): return None return model.itemByPath(self.text()) def eventFilter( self, object, event ): """ Filters the events for the inputed object through this edit. :param object | <QObject> event | <QEvent> :return <bool> | consumed """ if ( event.type() == event.KeyPress ): if ( event.key() == Qt.Key_Escape ): self._completerTree.hide() self.completer().popup().hide() self.cancelEdit() elif ( event.key() in (Qt.Key_Return, Qt.Key_Enter) ): self.acceptEdit() return True elif ( event.key() == Qt.Key_Tab ): if ( self.completer().popup().isVisible() ): text = str(self.completer().currentCompletion()) super(XNavigationEdit, self).setText(text) return True else: self.acceptEdit() return False elif ( event.type() == event.MouseButtonPress ): if ( not self._completerTree.rect().contains(event.pos()) ): self._completerTree.hide() self.completer().popup().hide() self.cancelEdit() return False def focusOutEvent( self, event ): """ Overloads the focus out event to cancel editing when the widget loses focus. :param event | <QFocusEvent> """ super(XNavigationEdit, self).focusOutEvent(event) self.cancelEdit() def handleButtonClick( self, button ): """ Handle the event when a user clicks on one of the part buttons. :param button | <QToolButton> """ path = button.property('path') is_completer = button.property('is_completer') # popup a completion menu if ( unwrapVariant(is_completer) ): model = self.navigationModel() if ( not model ): return sep = self.separator() path = str(unwrapVariant(path)) item = model.itemByPath(path, includeRoot = True) if ( not item ): return curr_path = str(self.text()).strip(self.separator()) curr_path = curr_path.replace(path, '').strip(self.separator()) child_name = '' if ( curr_path ): child_name = curr_path.split(self.separator())[0] index = model.indexFromItem(item) self._completerTree.move(QCursor.pos()) self._completerTree.setRootIndex(index) self._completerTree.verticalScrollBar().setValue(0) if ( child_name ): child_item = None for i in range(item.rowCount()): child = item.child(i) if ( child.text() == child_name ): child_item = child break if ( child_item ): child_index = model.indexFromItem(child_item) self._completerTree.setCurrentIndex(child_index) self._completerTree.scrollTo(child_index) self._completerTree.show() self._completerTree.setUpdatesEnabled(True) else: self.setText(unwrapVariant(path)) def keyPressEvent( self, event ): """ Overloads the key press event to listen for escape calls to cancel the parts editing. :param event | <QKeyPressEvent> """ if ( self.scrollWidget().isHidden() ): if ( event.key() == Qt.Key_Escape ): self.cancelEdit() return elif ( event.key() in (Qt.Key_Return, Qt.Key_Enter) ): self.acceptEdit() return elif ( event.key() == Qt.Key_A and event.modifiers() == Qt.ControlModifier ): self.startEdit() super(XNavigationEdit, self).keyPressEvent(event) def mouseDoubleClickEvent( self, event ): """ Overloads the system to enable editing when a user double clicks. :param event | <QMouseEvent> """ super(XNavigationEdit, self).mouseDoubleClickEvent(event) self.startEdit() def navigationModel( self ): """ Returns the navigation model linked with this edit. :return <XNavigationModel> || None """ return self._navigationModel def navigateToIndex( self, index ): """ Navigates to the inputed action's path. :param action | <QAction> """ self._completerTree.hide() item = self._navigationModel.itemFromIndex(index) self.setText(self._navigationModel.itemPath(item)) def parts( self ): """ Returns the parts that are used for this system. :return [<str>, ..] """ path = str(self.text()).strip(self.separator()) if ( not path ): return [] return path.split(self.separator()) def partsWidget( self ): """ Returns the widget that contains the parts system. :return <QScrollArea> """ return self._partsWidget def startEdit( self ): """ Rebuilds the pathing based on the parts. """ self._originalText = self.text() self.scrollWidget().hide() self.setFocus() self.selectAll() def rebuild( self ): """ Rebuilds the parts widget with the latest text. """ navitem = self.currentItem() if ( navitem ): navitem.initialize() self.setUpdatesEnabled(False) self.scrollWidget().show() self._originalText = '' partsw = self.partsWidget() for button in self._buttonGroup.buttons(): self._buttonGroup.removeButton(button) button.close() button.setParent(None) button.deleteLater() # create the root button layout = partsw.layout() parts = self.parts() button = QToolButton(partsw) button.setAutoRaise(True) button.setMaximumWidth(12) button.setArrowType(Qt.RightArrow) button.setProperty('path', wrapVariant('')) button.setProperty('is_completer', wrapVariant(True)) last_button = button self._buttonGroup.addButton(button) layout.insertWidget(0, button) # check to see if we have a navigation model setup if ( self._navigationModel ): last_item = self._navigationModel.itemByPath(self.text()) show_last = last_item and last_item.rowCount() > 0 else: show_last = False # load the navigation system count = len(parts) for i, part in enumerate(parts): path = self.separator().join(parts[:i+1]) button = QToolButton(partsw) button.setAutoRaise(True) button.setText(part) if ( self._navigationModel ): item = self._navigationModel.itemByPath(path) if ( item ): button.setIcon(item.icon()) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setProperty('path', wrapVariant(path)) button.setProperty('is_completer', wrapVariant(False)) self._buttonGroup.addButton(button) layout.insertWidget((i * 2) + 1, button) # determine if we should show the final button if ( show_last or i < (count - 1) ): button = QToolButton(partsw) button.setAutoRaise(True) button.setMaximumWidth(12) button.setArrowType(Qt.RightArrow) button.setProperty('path', wrapVariant(path)) button.setProperty('is_completer', wrapVariant(True)) self._buttonGroup.addButton(button) layout.insertWidget((i * 2) + 2, button) last_button = button if ( self.scrollWidget().width() < partsw.width() ): self.scrollParts(partsw.width() - self.scrollWidget().width()) self.setUpdatesEnabled(True) self.navigationChanged.emit() def resizeEvent( self, event ): """ Resizes the current widget and its parts widget. :param event | <QResizeEvent> """ super(XNavigationEdit, self).resizeEvent(event) w = self.width() h = self.height() self._scrollWidget.resize(w - 4, h - 4) if ( self._scrollWidget.width() < self._partsWidget.width() ): self.scrollParts( self._partsWidget.width() - self._scrollWidget.width() ) def scrollParts( self, amount ): """ Scrolls the parts to offset the scrolling amount. :param amount | <int> """ change = self._scrollAmount - amount self._partsWidget.scroll(change, 0) self._scrollAmount = amount def scrollWidget( self ): """ Returns the scrolling widget. :return <QScrollArea> """ return self._scrollWidget def separator( self ): """ Returns the separation character that is used for this edit. :return <str> """ return self._separator def setTopLevelItems( self, items ): """ Initializes the navigation system to start with the inputed root \ item. :param item | <XNavigationItem> """ if ( not self._navigationModel ): self.setNavigationModel(XNavigationModel(self)) self._navigationModel.setTopLevelItems(items) def setNavigationModel( self, model ): """ Sets the navigation model for this edit. :param model | <XNavigationModel> """ self._navigationModel = model self._completerTree.setModel(model) if ( model ): model.setSeparator(self.separator()) completer = XNavigationCompleter(model, self) self.setCompleter(completer) completer.popup().installEventFilter(self) else: self.setCompleter(None) self.rebuild() def setParts( self, parts ): """ Sets the path for this edit widget by providing the parts to the path. :param parts | [<str>, ..] """ self.setText(self.separator().join(map(str, parts))) def setSeparator( self, separator ): """ Sets the separator to the inputed character. :param separator | <str> """ self._separator = separator if ( self._navigationModel ): self._navigationModel.setSeparator(separator) self.rebuild() def setText( self, text ): """ Sets the text for this edit to the inputed text. :param text | <str> """ super(XNavigationEdit, self).setText(text) self.scrollWidget().show() if ( text == '' or self._originalText != text ): self.rebuild()
class XPopupWidget(QWidget): """ """ Direction = enum('North', 'South', 'East', 'West') Mode = enum('Popup', 'Dialog', 'ToolTip') Anchor = enum('TopLeft', 'TopCenter', 'TopRight', 'LeftTop', 'LeftCenter', 'LeftBottom', 'RightTop', 'RightCenter', 'RightBottom', 'BottomLeft', 'BottomCenter', 'BottomRight') aboutToShow = qt.Signal() accepted = qt.Signal() closed = qt.Signal() rejected = qt.Signal() resetRequested = qt.Signal() shown = qt.Signal() buttonClicked = qt.Signal(QAbstractButton) def __init__(self, parent=None, buttons=None): super(XPopupWidget, self).__init__(parent) # define custom properties self._anchor = XPopupWidget.Anchor.TopCenter self._autoCalculateAnchor = False self._autoCloseOnAccept = True self._autoCloseOnReject = True self._autoCloseOnFocusOut = False self._autoDefault = True self._first = True self._animated = False self._currentMode = None self._positionLinkedTo = [] # define controls self._resizable = True self._popupPadding = 10 self._titleBarVisible = True self._buttonBoxVisible = True self._dialogButton = QToolButton(self) self._closeButton = QToolButton(self) self._scrollArea = QScrollArea(self) self._sizeGrip = QSizeGrip(self) self._sizeGrip.setFixedWidth(12) self._sizeGrip.setFixedHeight(12) self._leftSizeGrip = QSizeGrip(self) self._leftSizeGrip.setFixedWidth(12) self._leftSizeGrip.setFixedHeight(12) if buttons is None: buttons = QDialogButtonBox.NoButton self._buttonBox = QDialogButtonBox(buttons, Qt.Horizontal, self) self._buttonBox.setContentsMargins(3, 0, 3, 9) self._scrollArea.setWidgetResizable(True) self._scrollArea.setFrameShape(QScrollArea.NoFrame) self._scrollArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) palette = self.palette() self._scrollArea.setPalette(palette) self._dialogButton.setToolTip('Popout to Dialog') self._closeButton.setToolTip('Close Popup') for btn in (self._dialogButton, self._closeButton): btn.setAutoRaise(True) btn.setIconSize(QSize(14, 14)) btn.setMaximumSize(16, 16) # setup the icons icon = QIcon(projexui.resources.find('img/dialog.png')) self._dialogButton.setIcon(icon) icon = QIcon(projexui.resources.find('img/close.png')) self._closeButton.setIcon(icon) # define the ui hlayout = QHBoxLayout() hlayout.setSpacing(0) hlayout.addStretch(1) hlayout.addWidget(self._dialogButton) hlayout.addWidget(self._closeButton) hlayout.setContentsMargins(0, 0, 0, 0) hlayout2 = QHBoxLayout() hlayout2.addWidget(self._buttonBox) hlayout2.setContentsMargins(0, 0, 3, 0) vlayout = QVBoxLayout() vlayout.addLayout(hlayout) vlayout.addWidget(self._scrollArea) vlayout.addLayout(hlayout2) vlayout.setContentsMargins(3, 2, 3, 2) vlayout.setSpacing(0) self.setLayout(vlayout) self.setPositionLinkedTo(parent) # set default properties self.setAutoFillBackground(True) self.setBackgroundRole(QPalette.Button) self.setWindowTitle('Popup') self.setFocusPolicy(Qt.StrongFocus) self.setCurrentMode(XPopupWidget.Mode.Popup) # create connections self._dialogButton.clicked.connect(self.setDialogMode) self._closeButton.clicked.connect(self.reject) self._buttonBox.accepted.connect(self.accept) self._buttonBox.rejected.connect(self.reject) self._buttonBox.clicked.connect(self.handleButtonClick) def addButton(self, button, role=QDialogButtonBox.ActionRole): """ Adds a custom button to the button box for this popup widget. :param button | <QAbstractButton> || <str> :return <button> || None (based on if a button or string was given) """ return self._buttonBox.addButton(button, role) def adjustContentsMargins(self): """ Adjusts the contents for this widget based on the anchor and \ mode. """ anchor = self.anchor() mode = self.currentMode() # margins for a dialog if (mode == XPopupWidget.Mode.Dialog): self.setContentsMargins(0, 0, 0, 0) # margins for a top anchor point elif (anchor & (XPopupWidget.Anchor.TopLeft | XPopupWidget.Anchor.TopCenter | XPopupWidget.Anchor.TopRight)): self.setContentsMargins(0, self.popupPadding() + 5, 0, 0) # margins for a bottom anchor point elif ( anchor & (XPopupWidget.Anchor.BottomLeft | XPopupWidget.Anchor.BottomCenter | XPopupWidget.Anchor.BottomRight)): self.setContentsMargins(0, 0, 0, self.popupPadding()) # margins for a left anchor point elif (anchor & (XPopupWidget.Anchor.LeftTop | XPopupWidget.Anchor.LeftCenter | XPopupWidget.Anchor.LeftBottom)): self.setContentsMargins(self.popupPadding(), 0, 0, 0) # margins for a right anchor point else: self.setContentsMargins(0, 0, self.popupPadding(), 0) self.adjustMask() def adjustMask(self): """ Updates the alpha mask for this popup widget. """ if self.currentMode() == XPopupWidget.Mode.Dialog: self.clearMask() return path = self.borderPath() bitmap = QBitmap(self.width(), self.height()) bitmap.fill(QColor('white')) painter = QPainter() painter.begin(bitmap) painter.setRenderHint(QPainter.Antialiasing) pen = QPen(QColor('black')) pen.setWidthF(0.75) painter.setPen(pen) painter.setBrush(QColor('black')) painter.drawPath(path) painter.end() self.setMask(bitmap) def adjustSize(self): """ Adjusts the size of this popup to best fit the new widget size. """ widget = self.centralWidget() if widget is None: super(XPopupWidget, self).adjustSize() return widget.adjustSize() hint = widget.minimumSizeHint() size = widget.minimumSize() width = max(size.width(), hint.width()) height = max(size.height(), hint.height()) width += 20 height += 20 if self._buttonBoxVisible: height += self.buttonBox().height() + 10 if self._titleBarVisible: height += max(self._dialogButton.height(), self._closeButton.height()) + 10 curr_w = self.width() curr_h = self.height() # determine if we need to move based on our anchor anchor = self.anchor() if anchor & (self.Anchor.LeftBottom | self.Anchor.RightBottom | \ self.Anchor.BottomLeft | self.Anchor.BottomCenter | \ self.Anchor.BottomRight): delta_y = height - curr_h elif anchor & (self.Anchor.LeftCenter | self.Anchor.RightCenter): delta_y = (height - curr_h) / 2 else: delta_y = 0 if anchor & (self.Anchor.RightTop | self.Anchor.RightCenter | \ self.Anchor.RightTop | self.Anchor.TopRight): delta_x = width - curr_w elif anchor & (self.Anchor.TopCenter | self.Anchor.BottomCenter): delta_x = (width - curr_w) / 2 else: delta_x = 0 self.setMinimumSize(width, height) self.resize(width, height) pos = self.pos() pos.setX(pos.x() - delta_x) pos.setY(pos.y() - delta_y) self.move(pos) @qt.Slot() def accept(self): """ Emits the accepted signal and closes the popup. """ if not self.signalsBlocked(): self.accepted.emit() if self.autoCloseOnAccept(): self.close() def anchor(self): """ Returns the anchor point for this popup widget. :return <XPopupWidget.Anchor> """ return self._anchor def autoCalculateAnchor(self): """ Returns whether or not this popup should calculate the anchor point on popup based on the parent widget and the popup point. :return <bool> """ return self._autoCalculateAnchor def autoCloseOnAccept(self): """ Returns whether or not this popup widget manages its own close on accept behavior. :return <bool> """ return self._autoCloseOnAccept def autoCloseOnReject(self): """ Returns whether or not this popup widget manages its own close on reject behavior. :return <bool> """ return self._autoCloseOnReject def autoCloseOnFocusOut(self): """ Returns whether or not this popup widget should auto-close when the user clicks off the view. :return <bool> """ return self._autoCloseOnFocusOut def autoDefault(self): """ Returns whether or not clicking enter should default to the accept key. :return <bool> """ return self._autoDefault def borderPath(self): """ Returns the border path that will be drawn for this widget. :return <QPainterPath> """ path = QPainterPath() x = 1 y = 1 w = self.width() - 2 h = self.height() - 2 pad = self.popupPadding() anchor = self.anchor() # create a path for a top-center based popup if anchor == XPopupWidget.Anchor.TopCenter: path.moveTo(x, y + pad) path.lineTo(x + ((w / 2) - pad), y + pad) path.lineTo(x + (w / 2), y) path.lineTo(x + ((w / 2) + pad), y + pad) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a top-left based popup elif anchor == XPopupWidget.Anchor.TopLeft: path.moveTo(x, y + pad) path.lineTo(x + pad, y) path.lineTo(x + 2 * pad, y + pad) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a top-right based popup elif anchor == XPopupWidget.Anchor.TopRight: path.moveTo(x, y + pad) path.lineTo(x + w - 2 * pad, y + pad) path.lineTo(x + w - pad, y) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a bottom-left based popup elif anchor == XPopupWidget.Anchor.BottomLeft: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + 2 * pad, y + h - pad) path.lineTo(x + pad, y + h) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a south based popup elif anchor == XPopupWidget.Anchor.BottomCenter: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + ((w / 2) + pad), y + h - pad) path.lineTo(x + (w / 2), y + h) path.lineTo(x + ((w / 2) - pad), y + h - pad) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a bottom-right based popup elif anchor == XPopupWidget.Anchor.BottomRight: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + w - pad, y + h) path.lineTo(x + w - 2 * pad, y + h - pad) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a right-top based popup elif anchor == XPopupWidget.Anchor.RightTop: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w, y + pad) path.lineTo(x + w - pad, y + 2 * pad) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a right-center based popup elif anchor == XPopupWidget.Anchor.RightCenter: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w - pad, y + ((h / 2) - pad)) path.lineTo(x + w, y + (h / 2)) path.lineTo(x + w - pad, y + ((h / 2) + pad)) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a right-bottom based popup elif anchor == XPopupWidget.Anchor.RightBottom: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w - pad, y + h - 2 * pad) path.lineTo(x + w, y + h - pad) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a left-top based popup elif anchor == XPopupWidget.Anchor.LeftTop: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x + pad, y + 2 * pad) path.lineTo(x, y + pad) path.lineTo(x + pad, y) # create a path for an left-center based popup elif anchor == XPopupWidget.Anchor.LeftCenter: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x + pad, y + ((h / 2) + pad)) path.lineTo(x, y + (h / 2)) path.lineTo(x + pad, y + ((h / 2) - pad)) path.lineTo(x + pad, y) # create a path for a left-bottom based popup elif anchor == XPopupWidget.Anchor.LeftBottom: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x, y + h - pad) path.lineTo(x + pad, y + h - 2 * pad) path.lineTo(x + pad, y) return path def buttonBox(self): """ Returns the button box that is used to control this popup widget. :return <QDialogButtonBox> """ return self._buttonBox def centralWidget(self): """ Returns the central widget that is being used by this popup. :return <QWidget> """ return self._scrollArea.widget() def close(self): """ Closes the popup widget and central widget. """ widget = self.centralWidget() if widget and not widget.close(): return super(XPopupWidget, self).close() def closeEvent(self, event): widget = self.centralWidget() if widget and not widget.close() and \ self.currentMode() != XPopupWidget.Mode.ToolTip: event.ignore() else: super(XPopupWidget, self).closeEvent(event) self.closed.emit() def currentMode(self): """ Returns the current mode for this widget. :return <XPopupWidget.Mode> """ return self._currentMode @deprecatedmethod('XPopupWidget', 'Direction is no longer used, use anchor instead') def direction(self): """ Returns the current direction parameter for this widget. :return <XPopupWidget.Direction> """ anchor = self.anchor() if (anchor & (XPopupWidget.Anchor.TopLeft | XPopupWidget.Anchor.TopCenter | XPopupWidget.Anchor.TopRight)): return XPopupWidget.Direction.North elif ( anchor & (XPopupWidget.Anchor.BottomLeft | XPopupWidget.Anchor.BottomCenter | XPopupWidget.Anchor.BottomRight)): return XPopupWidget.Direction.South elif (anchor & (XPopupWidget.Anchor.LeftTop | XPopupWidget.Anchor.LeftCenter | XPopupWidget.Anchor.LeftBottom)): return XPopupWidget.Direction.East else: return XPopupWidget.Direction.West def eventFilter(self, object, event): """ Processes when the window is moving to update the position for the popup if in popup mode. :param object | <QObject> event | <QEvent> """ links = self.positionLinkedTo() is_dialog = self.currentMode() == self.Mode.Dialog if object not in links: return False if event.type() == event.Close: self.close() return False if event.type() == event.Hide and not is_dialog: self.hide() return False if event.type() == event.Move and not is_dialog: deltaPos = event.pos() - event.oldPos() self.move(self.pos() + deltaPos) return False if self.currentMode() != self.Mode.ToolTip: return False if event.type() == event.Leave: pos = object.mapFromGlobal(QCursor.pos()) if (not object.rect().contains(pos)): self.close() event.accept() return True if event.type() in (event.MouseButtonPress, event.MouseButtonDblClick): self.close() event.accept() return True return False @qt.Slot(QAbstractButton) def handleButtonClick(self, button): """ Handles the button click for this widget. If the Reset button was clicked, then the resetRequested signal will be emitted. All buttons will emit the buttonClicked signal. :param button | <QAbstractButton> """ if (self.signalsBlocked()): return if (button == self._buttonBox.button(QDialogButtonBox.Reset)): self.resetRequested.emit() self.buttonClicked.emit(button) def isAnimated(self): """ Returns whether or not the popup widget should animate its opacity when it is shown. :return <bool> """ return self._animated def isResizable(self): """ Returns if this popup is resizable or not. :return <bool> """ return self._resizable def keyPressEvent(self, event): """ Looks for the Esc key to close the popup. :param event | <QKeyEvent> """ if (event.key() == Qt.Key_Escape): self.reject() event.accept() return elif (event.key() in (Qt.Key_Return, Qt.Key_Enter)): if self._autoDefault: self.accept() event.accept() return super(XPopupWidget, self).keyPressEvent(event) def mapAnchorFrom(self, widget, globalPos): """ Returns the anchor point that best fits within the given widget from the inputed global position. :param widget | <QWidget> globalPos | <QPoint> :return <XPopupWidget.Anchor> """ localPos = widget.mapFromGlobal(globalPos) x = localPos.x() y = localPos.y() w = widget.width() h = widget.height() cw = self.width() / 2 ch = self.height() / 2 # by default, try to do a center point, so make sure the center point # is at least 1/2 the width longer from each edge if x < cw and h - y < ch: return XPopupWidget.Anchor.BottomLeft elif x < cw: return XPopupWidget.Anchor.TopLeft elif w - x < cw and h - y < ch: return XPopupWidget.Anchor.BottomRight elif w - x < cw: return XPopupWidget.Anchor.TopRight elif h - y < ch: return XPopupWidget.Anchor.BottomCenter else: return XPopupWidget.Anchor.TopCenter def popup(self, pos=None): """ Pops up this widget at the inputed position. The inputed point should \ be in global space. :param pos | <QPoint> :return <bool> success """ if self._first and self.centralWidget() is not None: self.adjustSize() self._first = False if not self.signalsBlocked(): self.aboutToShow.emit() if not pos: pos = QCursor.pos() if self.currentMode() == XPopupWidget.Mode.Dialog and \ self.isVisible(): return False elif self.currentMode() == XPopupWidget.Mode.Dialog: self.setPopupMode() # auto-calculate the point if self.autoCalculateAnchor(): self.setAnchor(self.mapAnchorFrom(self.parent(), pos)) pad = self.popupPadding() # determine where to move based on the anchor anchor = self.anchor() # MODIFY X POSITION # align x-left if (anchor & (XPopupWidget.Anchor.TopLeft | XPopupWidget.Anchor.BottomLeft)): pos.setX(pos.x() - pad) # align x-center elif (anchor & (XPopupWidget.Anchor.TopCenter | XPopupWidget.Anchor.BottomCenter)): pos.setX(pos.x() - self.width() / 2) # align x-right elif ( anchor & (XPopupWidget.Anchor.TopRight | XPopupWidget.Anchor.BottomRight)): pos.setX(pos.x() - self.width() + pad) # align x-padded elif (anchor & (XPopupWidget.Anchor.RightTop | XPopupWidget.Anchor.RightCenter | XPopupWidget.Anchor.RightBottom)): pos.setX(pos.x() - self.width()) # MODIFY Y POSITION # align y-top if (anchor & (XPopupWidget.Anchor.LeftTop | XPopupWidget.Anchor.RightTop)): pos.setY(pos.y() - pad) # align y-center elif (anchor & (XPopupWidget.Anchor.LeftCenter | XPopupWidget.Anchor.RightCenter)): pos.setY(pos.y() - self.height() / 2) # align y-bottom elif (anchor & (XPopupWidget.Anchor.LeftBottom | XPopupWidget.Anchor.RightBottom)): pos.setY(pos.y() - self.height() + pad) # align y-padded elif ( anchor & (XPopupWidget.Anchor.BottomLeft | XPopupWidget.Anchor.BottomCenter | XPopupWidget.Anchor.BottomRight)): pos.setY(pos.y() - self.height()) self.adjustMask() self.move(pos) self.update() self.setUpdatesEnabled(True) if self.isAnimated(): anim = QPropertyAnimation(self, 'windowOpacity') anim.setParent(self) anim.setStartValue(0.0) anim.setEndValue(self.windowOpacity()) anim.setDuration(500) anim.finished.connect(anim.deleteLater) self.setWindowOpacity(0.0) else: anim = None self.show() if self.currentMode() != XPopupWidget.Mode.ToolTip: self.activateWindow() widget = self.centralWidget() if widget: self.centralWidget().setFocus() if anim: anim.start() if not self.signalsBlocked(): self.shown.emit() return True def paintEvent(self, event): """ Overloads the paint event to handle painting pointers for the popup \ mode. :param event | <QPaintEvent> """ # use the base technique for the dialog mode if self.currentMode() == XPopupWidget.Mode.Dialog: super(XPopupWidget, self).paintEvent(event) return # setup the coloring options palette = self.palette() painter = QPainter() painter.begin(self) pen = QPen(palette.color(palette.Window).darker(130)) pen.setWidthF(1.75) painter.setPen(pen) painter.setRenderHint(painter.Antialiasing) painter.setBrush(palette.color(palette.Window)) painter.drawPath(self.borderPath()) painter.end() def popupPadding(self): """ Returns the amount of pixels to pad the popup arrow for this widget. :return <int> """ return self._popupPadding def positionLinkedTo(self): """ Returns the widget that this popup is linked to for positional changes. :return [<QWidget>, ..] """ return self._positionLinkedTo @qt.Slot() def reject(self): """ Emits the accepted signal and closes the popup. """ if not self.signalsBlocked(): self.rejected.emit() if self.autoCloseOnReject(): self.close() def resizeEvent(self, event): """ Resizes this widget and updates the mask. :param event | <QResizeEvent> """ self.setUpdatesEnabled(False) super(XPopupWidget, self).resizeEvent(event) self.adjustMask() self.setUpdatesEnabled(True) x = self.width() - self._sizeGrip.width() y = self.height() - self._sizeGrip.height() self._leftSizeGrip.move(0, y) self._sizeGrip.move(x, y) def scrollArea(self): """ Returns the scroll area widget for this popup. :return <QScrollArea> """ return self._scrollArea def setAnimated(self, state): """ Sets whether or not the popup widget should animate its opacity when it is shown. :param state | <bool> """ self._animated = state self.setAttribute(Qt.WA_TranslucentBackground, state) def setAutoCloseOnAccept(self, state): """ Sets whether or not the popup handles closing for accepting states. :param state | <bool> """ self._autoCloseOnAccept = state def setAutoCloseOnReject(self, state): """ Sets whether or not the popup handles closing for rejecting states. :param state | <bool> """ self._autoCloseOnReject = state def setAutoDefault(self, state): """ Sets whether or not the buttons should respond to defaulting options when the user is interacting with it. :param state | <bool> """ self._autoDefault = state for button in self.buttonBox().buttons(): button.setAutoDefault(state) button.setDefault(state) def setAnchor(self, anchor): """ Sets the anchor position for this popup widget to the inputed point. :param anchor | <XPopupWidget.Anchor> """ self._anchor = anchor self.adjustContentsMargins() def setAutoCalculateAnchor(self, state): """ Sets whether or not this widget should auto-calculate the anchor point based on the parent position when the popup is triggered. :param state | <bool> """ self._autoCalculateAnchor = state def setAutoCloseOnFocusOut(self, state): """ Sets whether or not this popup widget should auto-close when the user clicks off the view. :param state | <bool> """ self._autoCloseOnFocusOut = state self.updateModeSettings() def setCentralWidget(self, widget): """ Sets the central widget that will be used by this popup. :param widget | <QWidget> || None """ self._scrollArea.takeWidget() self._scrollArea.setWidget(widget) self.adjustSize() def setCurrentMode(self, mode): """ Sets the current mode for this dialog to the inputed mode. :param mode | <XPopupWidget.Mode> """ if (self._currentMode == mode): return self._currentMode = mode self.updateModeSettings() @qt.Slot() def setDialogMode(self): """ Sets the current mode value to Dialog. """ self.setCurrentMode(XPopupWidget.Mode.Dialog) @deprecatedmethod('XPopupWidget', 'Direction is no longer used, use setAnchor instead') def setDirection(self, direction): """ Sets the direction for this widget to the inputed direction. :param direction | <XPopupWidget.Direction> """ if (direction == XPopupWidget.Direction.North): self.setAnchor(XPopupWidget.Anchor.TopCenter) elif (direction == XPopupWidget.Direction.South): self.setAnchor(XPopupWidget.Anchor.BottomCenter) elif (direction == XPopupWidget.Direction.East): self.setAnchor(XPopupWidget.Anchor.LeftCenter) else: self.setAnchor(XPopupWidget.Anchor.RightCenter) def setPalette(self, palette): """ Sets the palette for this widget and the scroll area. :param palette | <QPalette> """ super(XPopupWidget, self).setPalette(palette) self._scrollArea.setPalette(palette) def setPopupMode(self): """ Sets the current mode value to Popup. """ self.setCurrentMode(XPopupWidget.Mode.Popup) def setPopupPadding(self, padding): """ Sets the amount to pad the popup area when displaying this widget. :param padding | <int> """ self._popupPadding = padding self.adjustContentsMargins() def setPositionLinkedTo(self, widgets): """ Sets the widget that this popup will be linked to for positional changes. :param widgets | <QWidget> || [<QWidget>, ..] """ if type(widgets) in (list, set, tuple): new_widgets = list(widgets) else: new_widgets = [] widget = widgets while widget: widget.installEventFilter(self) new_widgets.append(widget) widget = widget.parent() self._positionLinkedTo = new_widgets def setResizable(self, state): self._resizable = state self._sizeGrip.setVisible(state) self._leftSizeGrip.setVisible(state) def setShowButtonBox(self, state): self._buttonBoxVisible = state self.buttonBox().setVisible(state) def setShowTitleBar(self, state): self._titleBarVisible = state self._dialogButton.setVisible(state) self._closeButton.setVisible(state) def setToolTipMode(self): """ Sets the mode for this popup widget to ToolTip """ self.setCurrentMode(XPopupWidget.Mode.ToolTip) def setVisible(self, state): super(XPopupWidget, self).setVisible(state) widget = self.centralWidget() if widget: widget.setVisible(state) def timerEvent(self, event): """ When the timer finishes, hide the tooltip popup widget. :param event | <QEvent> """ if self.currentMode() == XPopupWidget.Mode.ToolTip: self.killTimer(event.timerId()) event.accept() self.close() else: super(XPopupWidget, self).timerEvent(event) def updateModeSettings(self): mode = self.currentMode() is_visible = self.isVisible() # display as a floating dialog if mode == XPopupWidget.Mode.Dialog: self.setWindowFlags(Qt.Dialog | Qt.Tool) self.setAttribute(Qt.WA_TransparentForMouseEvents, False) self._closeButton.setVisible(False) self._dialogButton.setVisible(False) # display as a user tooltip elif mode == XPopupWidget.Mode.ToolTip: flags = Qt.Popup | Qt.FramelessWindowHint self.setWindowFlags(flags) self.setBackgroundRole(QPalette.Window) self.setAttribute(Qt.WA_TransparentForMouseEvents) self.setShowTitleBar(False) self.setShowButtonBox(False) self.setFocusPolicy(Qt.NoFocus) # hide the scrollbars policy = Qt.ScrollBarAlwaysOff self._scrollArea.setVerticalScrollBarPolicy(policy) self._scrollArea.setHorizontalScrollBarPolicy(policy) # display as a popup widget else: flags = Qt.Popup | Qt.FramelessWindowHint if not self.autoCloseOnFocusOut(): flags |= Qt.Tool self.setWindowFlags(flags) self._closeButton.setVisible(self._titleBarVisible) self._dialogButton.setVisible(self._titleBarVisible) self.setBackgroundRole(QPalette.Button) self.adjustContentsMargins() if (is_visible): self.show() @staticmethod @deprecatedmethod('XPopupWidget', 'This method no longer has an effect as we are not '\ 'storing references to the tooltip.') def hideToolTip(key=None): """ Hides any existing tooltip popup widgets. :warning This method is deprecated! """ pass @staticmethod def showToolTip(text, point=None, anchor=None, parent=None, background=None, foreground=None, key=None, seconds=5): """ Displays a popup widget as a tooltip bubble. :param text | <str> point | <QPoint> || None anchor | <XPopupWidget.Mode.Anchor> || None parent | <QWidget> || None background | <QColor> || None foreground | <QColor> || None key | <str> || None seconds | <int> """ if point is None: point = QCursor.pos() if parent is None: parent = QApplication.activeWindow() if anchor is None and parent is None: anchor = XPopupWidget.Anchor.TopCenter # create a new tooltip widget widget = XPopupWidget(parent) widget.setToolTipMode() widget.setResizable(False) # create the tooltip label label = QLabel(text, widget) label.setOpenExternalLinks(True) label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) label.setMargin(3) label.setIndent(3) label.adjustSize() widget.setCentralWidget(label) # update the tip label.adjustSize() widget.adjustSize() palette = widget.palette() if not background: background = palette.color(palette.ToolTipBase) if not foreground: foreground = palette.color(palette.ToolTipText) palette.setColor(palette.Window, QColor(background)) palette.setColor(palette.WindowText, QColor(foreground)) widget.setPalette(palette) widget.centralWidget().setPalette(palette) if anchor is None: widget.setAutoCalculateAnchor(True) else: widget.setAnchor(anchor) widget.setAutoCloseOnFocusOut(True) widget.setAttribute(Qt.WA_DeleteOnClose) widget.popup(point) widget.startTimer(1000 * seconds) return widget
class EntityEditorDialog(QDialog, MapperMixin): """ Dialog for editing entity attributes. """ addedModel = pyqtSignal(object) def __init__( self, entity, model=None, parent=None, manage_documents=True, collect_model=False ): """ Class constructor. :param entity: Entity object corresponding to a table object. :type entity: Entity :param model: Data object for loading data into the form widgets. If the model is set, then the editor dialog is assumed to be in edit mode. :type model: object :param parent: Parent widget that the form belongs to. :type parent: QWidget :param manage_documents: True if the dialog should provide controls for managing supporting documents. Only applicable if the entity allows for supporting documents to be attached. :type manage_documents: bool :param collect_model: If set to True only returns the filled form model without saving it to the database. :type collect_model: Boolean :return: If collect_model, returns SQLAlchemy Model """ QDialog.__init__(self, parent) self.collection_suffix = self.tr('Collection') #Set minimum width self.setMinimumWidth(350) #Flag for mandatory columns self.has_mandatory = False self._entity = entity self._fk_browsers = OrderedDict() #Set notification layout bar self.vlNotification = QVBoxLayout() self.vlNotification.setObjectName('vlNotification') self._notifBar = NotificationBar(self.vlNotification) # Set manage documents only if the entity supports documents if self._entity.supports_documents: self._manage_documents = manage_documents else: self._manage_documents = False #Setup entity model self._ent_document_model = None if self._entity.supports_documents: ent_model, self._ent_document_model = entity_model( self._entity, with_supporting_document=True ) else: ent_model = entity_model(self._entity) if not model is None: ent_model = model MapperMixin.__init__(self, ent_model) self.collect_model = collect_model #Initialize UI setup self._init_gui() #Set title editor_trans = self.tr('Editor') title = u'{0} {1}'.format( format_name(self._entity.short_name), editor_trans ) self.setWindowTitle(title) def _init_gui(self): #Setup base elements self.gridLayout = QGridLayout(self) self.gridLayout.setObjectName('glMain') self.gridLayout.addLayout( self.vlNotification, 0, 0, 1, 1 ) #Set column widget area column_widget_area = self._setup_columns_content_area() self.gridLayout.addWidget( column_widget_area, 1, 0, 1, 1 ) #Add notification for mandatory columns if applicable next_row = 2 if self.has_mandatory: self.required_fields_lbl = QLabel(self) msg = self.tr( 'Please fill out all required (*) fields.' ) msg = self._highlight_asterisk(msg) self.required_fields_lbl.setText(msg) self.gridLayout.addWidget( self.required_fields_lbl, next_row, 0, 1, 2 ) #Bump up row reference next_row += 1 self.buttonBox = QDialogButtonBox(self) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons( QDialogButtonBox.Cancel|QDialogButtonBox.Save ) self.buttonBox.setObjectName('buttonBox') self.gridLayout.addWidget( self.buttonBox, next_row, 0, 1, 1 ) if self.collect_model: self.buttonBox.accepted.connect( self.on_model_added ) self.buttonBox.rejected.connect( self.cancel ) else: #Connect to MapperMixin slots self.buttonBox.accepted.connect( self.submit ) self.buttonBox.rejected.connect( self.cancel ) def on_model_added(self): model = self.submit(True) self.addedModel.emit(model) def _setup_columns_content_area(self): #Only use this if entity supports documents self.entity_tab_widget = None self.doc_widget = None self.entity_scroll_area = QScrollArea(self) self.entity_scroll_area.setFrameShape(QFrame.NoFrame) self.entity_scroll_area.setWidgetResizable(True) self.entity_scroll_area.setObjectName('scrollArea') self.scroll_widget_contents = QWidget() self.scroll_widget_contents.setObjectName( 'scrollAreaWidgetContents' ) #Grid layout for controls self.gl = QGridLayout(self.scroll_widget_contents) self.gl.setObjectName('gl_widget_contents') #Append column labels and widgets table_name = self._entity.name columns = table_column_names(table_name) #Iterate entity column and assert if they exist row_id = 0 for c in self._entity.columns.values(): if not c.name in columns and not isinstance(c, VirtualColumn): continue #Get widget factory column_widget = ColumnWidgetRegistry.create( c, self.scroll_widget_contents ) if not column_widget is None: header = c.header() self.c_label = QLabel(self.scroll_widget_contents) #Format label text if it is a mandatory field if c.mandatory: header = '{0} *'.format(c.header()) #Highlight asterisk header = self._highlight_asterisk(header) self.c_label.setText(header) self.gl.addWidget(self.c_label, row_id, 0, 1, 1) self.column_widget = column_widget self.gl.addWidget(self.column_widget, row_id, 1, 1, 1) #Add user tip if specified for the column configuration if c.user_tip: self.tip_lbl = UserTipLabel(user_tip=c.user_tip) self.gl.addWidget(self.tip_lbl, row_id, 2, 1, 1) if c.mandatory and not self.has_mandatory: self.has_mandatory = True col_name = c.name #Replace name accordingly based on column type if isinstance(c, MultipleSelectColumn): col_name = c.model_attribute_name #Add widget to MapperMixin collection self.addMapping( col_name, self.column_widget, c.mandatory, pseudoname=c.header() ) #Bump up row_id row_id += 1 self.entity_scroll_area.setWidget( self.scroll_widget_contents ) #Check if there are children and add foreign key browsers ch_entities = self.children_entities() if len(ch_entities) > 0: if self.entity_tab_widget is None: self.entity_tab_widget = QTabWidget(self) #Add primary tab if necessary self._add_primary_attr_widget() for ch in ch_entities: self._add_fk_browser(ch) #Add tab widget if entity supports documents if self._entity.supports_documents: self.doc_widget = SupportingDocumentsWidget( self._entity.supporting_doc, self._ent_document_model, self ) #Map the source document manager object self.addMapping( 'documents', self.doc_widget.source_document_manager ) if self.entity_tab_widget is None: self.entity_tab_widget = QTabWidget(self) #Add attribute tab self._add_primary_attr_widget() #Add supporting documents tab self.entity_tab_widget.addTab( self.doc_widget, self.tr('Supporting Documents') ) #Return the correct widget if not self.entity_tab_widget is None: return self.entity_tab_widget return self.entity_scroll_area def _add_primary_attr_widget(self): # Check if the primary entity # exists and add if it does not pr_txt = self.tr('Primary') if not self.entity_tab_widget is None: tab_txt = self.entity_tab_widget.tabText(0) if not tab_txt == pr_txt: self.entity_tab_widget.addTab( self.entity_scroll_area, pr_txt ) def _add_fk_browser(self, child_entity): # Create and add foreign key # browser to the collection attr = u'{0}_collection'.format( child_entity.name ) #Return if the attribute does not exist if not hasattr(self._model, attr): return fkb = ForeignKeyMapper( child_entity, self, notification_bar=self._notifBar, can_filter=True ) #Add to mapped collection self.addMapping( attr, fkb ) self.entity_tab_widget.addTab( fkb, u'{0} {1}'.format( child_entity.short_name, self.collection_suffix ) ) #Add to the collection self._fk_browsers[child_entity.name] = fkb def children_entities(self): """ :return: Returns a list of children entities that refer to the main entity as the parent. :rtype: list """ return [ch for ch in self._entity.children() if ch.TYPE_INFO == Entity.TYPE_INFO] def document_widget(self): """ :return: Returns the widget for managing the supporting documents for an entity if enabled. :rtype: SupportingDocumentsWidget """ return self.doc_widget def source_document_manager(self): """ :return: Returns an instance of the SourceDocumentManager only if supporting documents are enabled for the given entity. Otherwise, None if supporting documents are not enabled. :rtype: SourceDocumentManager """ if self.doc_widget is None: return None return self.doc_widget.source_document_manager def _highlight_asterisk(self, text): #Highlight asterisk in red c = '*' #Do not format if there is no asterisk if text.find(c) == -1: return text asterisk_highlight = '<span style=\" color:#ff0000;\">*</span>' text = text.replace(c, asterisk_highlight) return u'<html><head/><body><p>{0}</p></body></html>'.format(text)
class AdvancedSearch(EntityEditorDialog): def __init__(self, entity, parent): EntityEditorDialog.__init__(self, entity, parent=parent) self.parent = parent def _init_gui(self): # Setup base elements self.gridLayout = QGridLayout(self) self.gridLayout.setObjectName('glMain') self.gridLayout.addLayout( self.vlNotification, 0, 0, 1, 1 ) QApplication.processEvents() column_widget_area = self._setup_columns_content_area() self.gridLayout.addWidget( column_widget_area, 1, 0, 1, 1 ) QApplication.processEvents() # Add notification for mandatory columns if applicable next_row = 2 # Set title search_trans = self.tr('Advanced Search') if self._entity.label is not None: if self._entity.label != '': title_str = self._entity.label else: title_str = format_name(self._entity.short_name) else: title_str = format_name(self._entity.short_name) title = u'{0} {1}'.format(title_str, search_trans) self.do_not_check_dirty = True self.setWindowTitle(title) # if self.has_mandatory: # self.required_fields_lbl = QLabel(self) # msg = self.tr( # 'Please fill out all required (*) fields.' # ) # msg = self._highlight_asterisk(msg) # self.required_fields_lbl.setText(msg) # self.gridLayout.addWidget( # self.required_fields_lbl, next_row, 0, 1, 2 # ) # # Bump up row reference # next_row += 1 self.buttonBox = QDialogButtonBox(self) self.buttonBox.setObjectName('buttonBox') self.gridLayout.addWidget( self.buttonBox, next_row, 0, 1, 1 ) self.buttonBox.setOrientation(Qt.Horizontal) self.search = QPushButton( QApplication.translate( 'EntityEditorDialog', 'Search' ) ) self.buttonBox.addButton( self.search, QDialogButtonBox.ActionRole ) self.buttonBox.setStandardButtons( QDialogButtonBox.Cancel ) self.search.clicked.connect(self.on_search) # # # # edit model, collect model # # adding new record for child # # # Saving in parent editor # if not isinstance(self._parent._parent, EntityEditorDialog): # # adding a new record # if self.edit_model is None: # # saving when digitizing. # if self.collect_model: # self.buttonBox.accepted.connect(self.on_model_added) # # saving parent editor # else: # self.buttonBox.accepted.connect(self.save_parent_editor) # self.save_new_button.clicked.connect(self.save_and_new) # # updating existing record # else: # if not self.collect_model: # # updating existing record of the parent editor # self.buttonBox.accepted.connect(self.save_parent_editor) # else: # self.buttonBox.accepted.connect(self.on_model_added) # # Saving in child editor # else: # # save and new record # if self.edit_model is None: # self.buttonBox.accepted.connect(self.on_child_saved) # self.save_new_button.clicked.connect( # lambda: self.on_child_saved(True) # ) # # else: # # When updating an existing child editor save to the db # self.buttonBox.accepted.connect( # self.on_child_saved # ) # #self.buttonBox.accepted.connect(self.submit) # self.buttonBox.rejected.connect(self.cancel) def on_search(self): search_data = {} for column in self._entity.columns.values(): if column.name in entity_display_columns(self._entity): if column.name == 'id': continue handler = self.attribute_mappers[ column.name].valueHandler() value = handler.value() if value != handler.default() and bool(value): search_data[column.name] = value # self.search_db(search_data) result = self.search_db_raw(search_data) self.parent._tableModel.removeRows(0, self.parent._tableModel.rowCount()) if result is not None: found = QApplication.translate('AdvancedSearch', 'records found') new_title = '{} - {} {}'.format(self.title, result.rowcount, found) if result.rowcount > 3000: title = QApplication.translate( 'AdvancedSearch', 'Advanced Search' ) message = QApplication.translate( 'AdvancedSearch', 'The search result returned {0} records, which is above the ' 'search result limit. <br>Would you like to see the first 3000 ' 'records?'.format("{:,}".format(result.rowcount )) ) res, chk_result = simple_dialog(self, title, message) if res: self.setWindowTitle(new_title) self.parent._initializeData(result) else: return else: self.setWindowTitle(new_title) self.parent._initializeData(result) def search_db(self, search_data): ent_model_obj = self.ent_model() # query = ent_model_obj.queryObject() for attr, value in search_data.iteritems(): ent_model_obj.queryObject().filter( getattr(self.ent_model(), attr) == value) # now we can run the query # print ent_model_obj.queryObject(), vars(ent_model_obj.queryObject()) # print str(ent_model_obj.queryObject()) results = ent_model_obj.queryObject().all() def search_db_raw(self, search_data): sql = u"SELECT * FROM {} WHERE ".format(self._entity.name) # query = ent_model_obj.queryObject() param = [] if len(search_data) == 0: return None for attr, value in search_data.iteritems(): if isinstance(value, (int, float)): param.append(u'{} = {}'.format(unicode(attr), unicode(value))) if isinstance(value, (unicode, str)): param.append(u"{} = '{}'".format(unicode(attr), unicode(value))) final_sql = u'{} {}'.format(sql, ' AND '.join(param)) # sql_text = text(final_sql) results = fetch_with_filter(final_sql) # now we can run the query return results def _setup_columns_content_area(self): # Only use this if entity supports documents # self.entity_tab_widget = None self.doc_widget = None self.entity_scroll_area = QScrollArea(self) self.entity_scroll_area.setFrameShape(QFrame.NoFrame) self.entity_scroll_area.setWidgetResizable(True) self.entity_scroll_area.setObjectName('scrollArea') # Grid layout for controls self.gl = QGridLayout(self.scroll_widget_contents) self.gl.setObjectName('gl_widget_contents') # Append column labels and widgets table_name = self._entity.name columns = table_column_names(table_name) # Iterate entity column and assert if they exist row_id = 0 for c, column_widget in self.column_widgets.iteritems(): if c.name in self.exclude_columns: continue if isinstance(c, MultipleSelectColumn): continue if not c.name in columns and not isinstance(c, VirtualColumn): continue if column_widget is not None: header = c.ui_display() self.c_label = QLabel(self.scroll_widget_contents) self.c_label.setText(header) self.gl.addWidget(self.c_label, row_id, 0, 1, 1) if c.TYPE_INFO == 'AUTO_GENERATED': column_widget.setReadOnly(False) column_widget.btn_load.hide() self.gl.addWidget(column_widget, row_id, 1, 1, 1) col_name = c.name # Add widget to MapperMixin collection self.addMapping( col_name, column_widget, c.mandatory, pseudoname=c.ui_display() ) # Bump up row_id row_id += 1 self.entity_scroll_area.setWidget(self.scroll_widget_contents) if self.entity_tab_widget is None: self.entity_tab_widget = QTabWidget(self) # Check if there are children and add foreign key browsers # Add primary tab if necessary self._add_primary_attr_widget() # self.entity_tab_widget.setTabEnabled(0, False) # enable/disable the tab # set the style sheet self.setStyleSheet( "QTabBar::tab::selected {width: 0; height: 0; margin: 0; " "padding: 0; border: none;} ") # Return the correct widget if self.entity_tab_widget is not None: return self.entity_tab_widget return self.entity_scroll_area def closeEvent(self, event): ''' Raised when a request to close the window is received. Check the dirty state of input controls and prompt user to save if dirty. ''' event.accept() def cancel(self): ''' Slot for closing the dialog. Checks the dirty state first before closing. ''' self.reject()
class SimpleEditor(Editor): """ Simple style of editor for lists, which displays a scrolling list box with only one item visible at a time. A icon next to the list box displays a menu of operations on the list. """ # -- Class Constants -------------------------------------------------------- # Whether the list is displayed in a single row: single_row = True # Menu for modifying the list list_menu = """ Add &Before [_menu_before]: self.add_before() Add &After [_menu_after]: self.add_after() --- &Delete [_menu_delete]: self.delete_item() --- Move &Up [_menu_up]: self.move_up() Move &Down [_menu_down]: self.move_down() Move to &Top [_menu_top]: self.move_top() Move to &Bottom [_menu_bottom]: self.move_bottom() """ empty_list_menu = """ Add: self.add_empty() """ # -- Facet Definitions ------------------------------------------------------ # The kind of editor to create for each list item: kind = Str # Is the list of items being edited mutable? mutable = Bool(True) # -- Public Methods --------------------------------------------------------- def init(self, parent): """ Finishes initializing the editor by creating the underlying toolkit widget. """ # Initialize the facet handler to use: facet_handler = self.factory.facet_handler if facet_handler is None: facet_handler = self.object.base_facet(self.name).handler self._facet_handler = facet_handler # Create a scrolled window to hold all of the list item controls: self.control = QScrollArea(parent) self.control.setFrameShape(QFrame.NoFrame) # Create a widget with a grid layout as the container. self._list_pane = QWidget() layout = QGridLayout(self._list_pane) layout.setMargin(0) # Remember the editor to use for each individual list item: editor = self.factory.editor if editor is None: editor = facet_handler.item_facet.get_editor() self._editor = getattr(editor, self.kind) # Set up the additional 'list items changed' event handler needed for # a list based facet: self.context_object.on_facet_set(self.update_editor_item, self.extended_name + "_items?", dispatch="ui") self.set_tooltip() def dispose(self): """ Disposes of the contents of an editor. """ self.context_object.on_facet_set(self.update_editor_item, self.extended_name + "_items?", remove=True) super(SimpleEditor, self).dispose() def update_editor(self): """ Updates the editor when the object facet changes externally to the editor. """ # Disconnect the editor from any control about to be destroyed: self._dispose_items() list_pane = self._list_pane layout = list_pane.layout() # Create all of the list item facet editors: facet_handler = self._facet_handler resizable = (facet_handler.minlen != facet_handler.maxlen) and self.mutable item_facet = facet_handler.item_facet values = self.value index = 0 is_fake = resizable and (len(values) == 0) if is_fake: values = [item_facet.default_value()[1]] editor = self._editor # FIXME: Add support for more than one column. for value in values: if resizable: control = IconButton("@facets:list_editor", self.popup_menu) layout.addWidget(control, index, 0) try: proxy = ListItemProxy(self.object, self.name, index, item_facet, value) if resizable: control.proxy = proxy peditor = editor(self.ui, proxy, "value", self.description).set(object_name="") peditor.prepare(list_pane) pcontrol = peditor.control pcontrol.proxy = proxy except: if not is_fake: raise pcontrol = QPushButton("sample", list_pane) if isinstance(pcontrol, QWidget): layout.addWidget(pcontrol, index, 1) else: layout.addLayout(pcontrol, index, 1) index += 1 if is_fake: self._cur_control = control self.empty_list() control.setParent(None) if self.single_row: rows = 1 else: rows = self.factory.rows # list_pane.SetSize( wx.Size( # width + ((facet_handler.maxlen > rows) * scrollbar_dx), # height * rows ) ) # QScrollArea can have problems if the widget being scrolled is set too # early (ie. before it contains something). if self.control.widget() is None: self.control.setWidget(list_pane) def update_editor_item(self, object, name, old, event): """ Updates the editor when an item in the object facet changes externally to the editor. """ # If this is not a simple, single item update, rebuild entire editor: if (len(event.removed) != 1) or (len(event.added) != 1): self.update_editor() return # Otherwise, find the proxy for this index and update it with the # changed value: for control in self.control.widget().children(): if isinstance(control, QLayout): continue proxy = control.proxy if proxy.index == event.index: proxy.value = event.added[0] break def empty_list(self): """ Creates an empty list entry (so the user can add a new item). """ control = IconButton("@facets:list_editor", self.popup_menu) control.is_empty = True proxy = ListItemProxy(self.object, self.name, -1, None, None) pcontrol = QLabel(" (Empty List)") pcontrol.proxy = control.proxy = proxy self.reload_sizer([(control, pcontrol)]) def reload_sizer(self, controls, extra=0): """ Reloads the layout from the specified list of ( button, proxy ) pairs. """ layout = self._list_pane.layout() child = layout.takeAt(0) while child is not None: child = layout.takeAt(0) del child index = 0 for control, pcontrol in controls: layout.addWidget(control) layout.addWidget(pcontrol) control.proxy.index = index index += 1 def get_info(self): """ Returns the associated object list and current item index. """ proxy = self._cur_control.proxy return (proxy.list, proxy.index) def popup_empty_menu(self, control): """ Displays the empty list editor popup menu. """ self._cur_control = control control.PopupMenuXY(MakeMenu(self.empty_list_menu, self, True, control).menu, 0, 0) def popup_menu(self): """ Displays the list editor popup menu. """ self._cur_control = control = self.control.sender() proxy = control.proxy index = proxy.index menu = MakeMenu(self.list_menu, self, True, control).menu len_list = len(proxy.list) not_full = len_list < self._facet_handler.maxlen self._menu_before.enabled(not_full) self._menu_after.enabled(not_full) self._menu_delete.enabled(len_list > self._facet_handler.minlen) self._menu_up.enabled(index > 0) self._menu_top.enabled(index > 0) self._menu_down.enabled(index < (len_list - 1)) self._menu_bottom.enabled(index < (len_list - 1)) menu.exec_(control.mapToGlobal(QPoint(0, 0))) def add_item(self, offset): """ Adds a new value at the specified list index. """ list, index = self.get_info() index += offset item_facet = self._facet_handler.item_facet dv = item_facet.default_value() if dv[0] == 7: func, args, kw = dv[1] if kw is None: kw = {} value = func(*args, **kw) else: value = dv[1] self.value = list[:index] + [value] + list[index:] self.update_editor() def add_before(self): """ Inserts a new item before the current item. """ self.add_item(0) def add_after(self): """ Inserts a new item after the current item. """ self.add_item(1) def add_empty(self): """ Adds a new item when the list is empty. """ list, index = self.get_info() self.add_item(0) def delete_item(self): """ Delete the current item. """ list, index = self.get_info() self.value = list[:index] + list[index + 1 :] self.update_editor() def move_up(self): """ Move the current item up one in the list. """ list, index = self.get_info() self.value = list[: index - 1] + [list[index], list[index - 1]] + list[index + 1 :] def move_down(self): """ Moves the current item down one in the list. """ list, index = self.get_info() self.value = list[:index] + [list[index + 1], list[index]] + list[index + 2 :] def move_top(self): """ Moves the current item to the top of the list. """ list, index = self.get_info() self.value = [list[index]] + list[:index] + list[index + 1 :] def move_bottom(self): """ Moves the current item to the bottom of the list. """ list, index = self.get_info() self.value = list[:index] + list[index + 1 :] + [list[index]] # -- Private Methods -------------------------------------------------------- def _dispose_items(self): """ Disposes of each current list item. """ list_pane = self._list_pane layout = list_pane.layout() for control in list_pane.children(): editor = getattr(control, "_editor", None) if editor is not None: editor.dispose() editor.control = None elif control is not layout: control.setParent(None) del control
def create_doc_tab_populate_combobox(self): """ Creates the supporting document component widget. """ self.doc_tab_data() self.docs_tab = QTabWidget() self.docs_tab_index = OrderedDict() for i, (id, doc) in enumerate(self.doc_types.iteritems()): self.docs_tab_index[doc] = i # the tab widget containing the document widget layout # and the child of the tab. tab_widget = QWidget() tab_widget.setObjectName(doc) # The layout of the tab widget cont_layout = QVBoxLayout(tab_widget) cont_layout.setObjectName( u'widget_layout_{}'.format(doc) ) # the scroll area widget inside the tab widget. scroll_area = QScrollArea(tab_widget) scroll_area.setFrameShape(QFrame.NoFrame) scroll_area.setObjectName( u'tab_scroll_area_{}'.format(doc) ) layout_widget = QWidget() # the widget the is under the scroll area content and # the widget containing the document widget layout # This widget is hidden and shown based on the STR number layout_widget.setObjectName( u'widget_{}'.format(doc) ) doc_widget_layout = QVBoxLayout(layout_widget) doc_widget_layout.setObjectName( u'doc_widget_layout_{}'.format( doc ) ) doc_widget = QWidget() doc_widget.setObjectName( u'doc_widget_{}_{}'.format(doc, self.str_number) ) doc_widget_layout.addWidget(doc_widget) # the layout containing document widget. ### This is the layout that is registered to add uploaded # supporting documents widgets into. tab_layout = QVBoxLayout(doc_widget) tab_layout.setObjectName( u'layout_{}_{}'.format(doc, self.str_number) ) scroll_area.setWidgetResizable(True) scroll_area.setWidget(layout_widget) cont_layout.addWidget(scroll_area) # Add the tab widget with the document # type name to create a tab. self.docs_tab.addTab(tab_widget, doc) self.container_box.addWidget(self.docs_tab, 1) if len(self.str_numbers) == 1: self.doc_type_cbo.addItem(doc, id)
# coding: utf-8 from PyQt4.QtCore import QRect from PyQt4.QtGui import (QDialog, QFrame, QLineEdit, QScrollArea, QSizePolicy, QVBoxLayout, QWidget) from qgis.gui import QgsCollapsibleGroupBox new_dialog = QDialog() new_dialog.resize(200, 100) scroll_area = QScrollArea(new_dialog) scroll_area.setFrameShape(QFrame.NoFrame) scroll_area.setFrameShadow(QFrame.Plain) scroll_area.setWidgetResizable(True) scroll_area.setGeometry(QRect(10, 20, 170, 70)) scrollAreaWidgetContents = QWidget() scrollAreaWidgetContents.setGeometry(QRect(0, 0, 170, 70)) vertical_layout = QVBoxLayout(scrollAreaWidgetContents) collapsible_group_box = QgsCollapsibleGroupBox(scrollAreaWidgetContents) collapsible_group_box.setTitle('Collapsible') sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( collapsible_group_box.sizePolicy().hasHeightForWidth() ) collapsible_group_box.setSizePolicy(sizePolicy) collapsible_group_box.setChecked(False) vbox_layout = QVBoxLayout(collapsible_group_box)
# coding: utf-8 from PyQt4.QtCore import QRect from PyQt4.QtGui import (QDialog, QFrame, QLineEdit, QScrollArea, QSizePolicy, QVBoxLayout, QWidget) from qgis.gui import QgsCollapsibleGroupBoxBasic new_dialog = QDialog() new_dialog.resize(200, 100) scroll_area = QScrollArea(new_dialog) scroll_area.setFrameShape(QFrame.NoFrame) scroll_area.setFrameShadow(QFrame.Plain) scroll_area.setWidgetResizable(True) scroll_area.setGeometry(QRect(10, 20, 170, 70)) scrollAreaWidgetContents = QWidget() scrollAreaWidgetContents.setGeometry(QRect(0, 0, 170, 70)) vertical_layout = QVBoxLayout(scrollAreaWidgetContents) collapsible_group_box_basic = QgsCollapsibleGroupBoxBasic( scrollAreaWidgetContents) collapsible_group_box_basic.setTitle('Collapsible') sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( collapsible_group_box_basic.sizePolicy().hasHeightForWidth()) collapsible_group_box_basic.setSizePolicy(sizePolicy) collapsible_group_box_basic.setChecked(False) vbox_layout = QVBoxLayout(collapsible_group_box_basic)
class AdvancedSearch(EntityEditorDialog): def __init__(self, entity, parent): EntityEditorDialog.__init__(self, entity, parent=parent) self.parent = parent def _init_gui(self): # Setup base elements self.gridLayout = QGridLayout(self) self.gridLayout.setObjectName('glMain') self.gridLayout.addLayout(self.vlNotification, 0, 0, 1, 1) QApplication.processEvents() column_widget_area = self._setup_columns_content_area() self.gridLayout.addWidget(column_widget_area, 1, 0, 1, 1) QApplication.processEvents() # Add notification for mandatory columns if applicable next_row = 2 # Set title search_trans = self.tr('Advanced Search') if self._entity.label is not None: if self._entity.label != '': title_str = self._entity.label else: title_str = format_name(self._entity.short_name) else: title_str = format_name(self._entity.short_name) title = u'{0} {1}'.format(title_str, search_trans) self.do_not_check_dirty = True self.setWindowTitle(title) # if self.has_mandatory: # self.required_fields_lbl = QLabel(self) # msg = self.tr( # 'Please fill out all required (*) fields.' # ) # msg = self._highlight_asterisk(msg) # self.required_fields_lbl.setText(msg) # self.gridLayout.addWidget( # self.required_fields_lbl, next_row, 0, 1, 2 # ) # # Bump up row reference # next_row += 1 self.buttonBox = QDialogButtonBox(self) self.buttonBox.setObjectName('buttonBox') self.gridLayout.addWidget(self.buttonBox, next_row, 0, 1, 1) self.buttonBox.setOrientation(Qt.Horizontal) self.search = QPushButton( QApplication.translate('EntityEditorDialog', 'Search')) self.buttonBox.addButton(self.search, QDialogButtonBox.ActionRole) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.search.clicked.connect(self.on_search) # # # # edit model, collect model # # adding new record for child # # # Saving in parent editor # if not isinstance(self._parent._parent, EntityEditorDialog): # # adding a new record # if self.edit_model is None: # # saving when digitizing. # if self.collect_model: # self.buttonBox.accepted.connect(self.on_model_added) # # saving parent editor # else: # self.buttonBox.accepted.connect(self.save_parent_editor) # self.save_new_button.clicked.connect(self.save_and_new) # # updating existing record # else: # if not self.collect_model: # # updating existing record of the parent editor # self.buttonBox.accepted.connect(self.save_parent_editor) # else: # self.buttonBox.accepted.connect(self.on_model_added) # # Saving in child editor # else: # # save and new record # if self.edit_model is None: # self.buttonBox.accepted.connect(self.on_child_saved) # self.save_new_button.clicked.connect( # lambda: self.on_child_saved(True) # ) # # else: # # When updating an existing child editor save to the db # self.buttonBox.accepted.connect( # self.on_child_saved # ) # #self.buttonBox.accepted.connect(self.submit) # self.buttonBox.rejected.connect(self.cancel) def on_search(self): search_data = {} for column in self._entity.columns.values(): if column.name in entity_display_columns(self._entity): if column.name == 'id': continue handler = self.attribute_mappers[column.name].valueHandler() value = handler.value() if value != handler.default() and bool(value): search_data[column.name] = value # self.search_db(search_data) result = self.search_db_raw(search_data) self.parent._tableModel.removeRows(0, self.parent._tableModel.rowCount()) if result is not None: found = QApplication.translate('AdvancedSearch', 'records found') new_title = '{} - {} {}'.format(self.title, result.rowcount, found) if result.rowcount > 3000: title = QApplication.translate('AdvancedSearch', 'Advanced Search') message = QApplication.translate( 'AdvancedSearch', 'The search result returned {0} records, which is above the ' 'search result limit. <br>Would you like to see the first 3000 ' 'records?'.format("{:,}".format(result.rowcount))) res, chk_result = simple_dialog(self, title, message) if res: self.setWindowTitle(new_title) self.parent._initializeData(result) else: return else: self.setWindowTitle(new_title) self.parent._initializeData(result) def search_db(self, search_data): ent_model_obj = self.ent_model() # query = ent_model_obj.queryObject() for attr, value in search_data.iteritems(): ent_model_obj.queryObject().filter( getattr(self.ent_model(), attr) == value) # now we can run the query # print ent_model_obj.queryObject(), vars(ent_model_obj.queryObject()) # print str(ent_model_obj.queryObject()) results = ent_model_obj.queryObject().all() def search_db_raw(self, search_data): sql = u"SELECT * FROM {} WHERE ".format(self._entity.name) # query = ent_model_obj.queryObject() param = [] if len(search_data) == 0: return None for attr, value in search_data.iteritems(): if isinstance(value, (int, float)): param.append(u'{} = {}'.format(unicode(attr), unicode(value))) if isinstance(value, (unicode, str)): param.append(u"{} = '{}'".format(unicode(attr), unicode(value))) final_sql = u'{} {}'.format(sql, ' AND '.join(param)) # sql_text = text(final_sql) results = fetch_with_filter(final_sql) # now we can run the query return results def _setup_columns_content_area(self): # Only use this if entity supports documents # self.entity_tab_widget = None self.doc_widget = None self.entity_scroll_area = QScrollArea(self) self.entity_scroll_area.setFrameShape(QFrame.NoFrame) self.entity_scroll_area.setWidgetResizable(True) self.entity_scroll_area.setObjectName('scrollArea') # Grid layout for controls self.gl = QGridLayout(self.scroll_widget_contents) self.gl.setObjectName('gl_widget_contents') # Append column labels and widgets table_name = self._entity.name columns = table_column_names(table_name) # Iterate entity column and assert if they exist row_id = 0 for c, column_widget in self.column_widgets.iteritems(): if c.name in self.exclude_columns: continue if isinstance(c, MultipleSelectColumn): continue if not c.name in columns and not isinstance(c, VirtualColumn): continue if column_widget is not None: header = c.ui_display() self.c_label = QLabel(self.scroll_widget_contents) self.c_label.setText(header) self.gl.addWidget(self.c_label, row_id, 0, 1, 1) if c.TYPE_INFO == 'AUTO_GENERATED': column_widget.setReadOnly(False) column_widget.btn_load.hide() self.gl.addWidget(column_widget, row_id, 1, 1, 1) col_name = c.name # Add widget to MapperMixin collection self.addMapping(col_name, column_widget, c.mandatory, pseudoname=c.ui_display()) # Bump up row_id row_id += 1 self.entity_scroll_area.setWidget(self.scroll_widget_contents) if self.entity_tab_widget is None: self.entity_tab_widget = QTabWidget(self) # Check if there are children and add foreign key browsers # Add primary tab if necessary self._add_primary_attr_widget() # self.entity_tab_widget.setTabEnabled(0, False) # enable/disable the tab # set the style sheet self.setStyleSheet( "QTabBar::tab::selected {width: 0; height: 0; margin: 0; " "padding: 0; border: none;} ") # Return the correct widget if self.entity_tab_widget is not None: return self.entity_tab_widget return self.entity_scroll_area def closeEvent(self, event): ''' Raised when a request to close the window is received. Check the dirty state of input controls and prompt user to save if dirty. ''' event.accept() def cancel(self): ''' Slot for closing the dialog. Checks the dirty state first before closing. ''' self.reject()
class PlotDialog(QDialog, PlotManager): """ Implements a dialog to which an arbitrary number of plots can be added. This class implements a `QDialog` with a number of plots on it. The axes of the plots can be arbitrarily synchronized and option checkboxes can be added which provide callbacks when the checkbox state changes. :param str wintitle: Title of the window. :param bool major_grid: Show major grid in plots. :param bool minor_grid: Show minor grid in plots. :param bool toolbar: Show toolbar. :param parent: Parent window. :param panels: A list of guiqwt panels to be added to the window. :param int min_plot_width: Default minimum width for plots. :param int min_plot_height: Default minimum height for plots. """ def __init__(self, wintitle='Plot window', major_grid=True, minor_grid=False, toolbar=True, parent=None, panels=None, min_plot_width=100, min_plot_height=75): QDialog.__init__(self, parent) self.setWindowFlags(Qt.Window) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(_fromUtf8(':/Application/Main')), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.setWindowIcon(icon) self.major_grid = major_grid self.minor_grid = minor_grid self.min_plot_width = min_plot_width self.min_plot_height = min_plot_height # WidgetMixin copy PlotManager.__init__(self, main=self) self.main_layout = QVBoxLayout(self) self.color_layout = QHBoxLayout() self.plot_layout = QGridLayout() self.plot_layout.setMargin(0) self.plot_scroll_widget = QWidget() self.plot_scroll_area = QScrollArea() self.plot_scroll_area.setFrameShape(QFrame.NoFrame) self.plot_scroll_area.setWidgetResizable(True) self.option_layout = QHBoxLayout() self.plot_widget = None if panels is not None: for panel in panels: self.add_panel(panel) self.toolbar = QToolBar('Tools') if not toolbar: self.toolbar.hide() # Configuring widget layout self._setup_widget_properties(wintitle=wintitle, icon=icon) self._setup_widget_layout() # Options self.option_callbacks = {} self.legend = None self.axis_syncplots = {} def _setup_widget_properties(self, wintitle, icon): self.setWindowTitle(wintitle) if isinstance(icon, basestring): icon = get_icon(icon) self.setWindowIcon(icon) self.setMinimumSize(320, 240) self.resize(720, 540) def _setup_widget_layout(self): self.main_layout.addWidget(self.toolbar) self.main_layout.addLayout(self.color_layout) self.main_layout.addWidget(self.plot_scroll_area) self.plot_scroll_area.setWidget(self.plot_scroll_widget) self.plot_scroll_widget.setLayout(self.plot_layout) self.main_layout.addLayout(self.option_layout) self.setLayout(self.main_layout) def add_custom_curve_tools(self, antialiasing=True, activate_zoom=True, signal_stats=False): """ Adds typically needed curve tools to the window. :param bool antialiasing: Determines if the antialiasing tool is added. :param bool activate_zoom: Determines if the zoom tool is activated initially (otherwise, the selection tool will be activated). :param bool signal_stats: Determines if the signal stats tool is available. """ self.add_toolbar(self.toolbar) t = self.add_tool(SelectTool) if not activate_zoom: self.set_default_tool(t) self.add_tool(BasePlotMenuTool, "item") self.add_tool(ExportItemDataTool) try: # Old versions of guiqwt and spyderlib do not support this import spyderlib.widgets.objecteditor from guiqwt.tools import EditItemDataTool self.add_tool(EditItemDataTool) except ImportError: pass self.add_tool(ItemCenterTool) self.add_tool(DeleteItemTool) self.add_separator_tool() t = self.add_tool(RectZoomTool) if activate_zoom: self.set_default_tool(t) self.add_tool(guiqwt_tools.HomeTool) self.add_tool(guiqwt_tools.PanTool) self.add_separator_tool() self.add_tool(BasePlotMenuTool, "grid") self.add_tool(BasePlotMenuTool, "axes") self.add_tool(DisplayCoordsTool) if self.get_itemlist_panel(): self.add_tool(ItemListPanelTool) if signal_stats: self.add_separator_tool() self.add_tool(SignalStatsTool) if antialiasing: self.add_tool(AntiAliasingTool) self.add_tool(AxisScaleTool) self.add_separator_tool() self.add_tool(SaveAsTool) self.add_tool(CopyToClipboardTool) self.add_tool(PrintTool) self.add_tool(guiqwt_tools.HelpTool) self.add_separator_tool() self.get_default_tool().activate() def add_custom_image_tools(self, activate_zoom=True): """ Adds typically needed image tools to the window. """ self.add_toolbar(self.toolbar) t = self.add_tool(SelectTool) if not activate_zoom: self.set_default_tool(t) self.add_tool(BasePlotMenuTool, "item") self.add_tool(ExportItemDataTool) try: # Old versions of guiqwt and spyderlib do not support this import spyderlib.widgets.objecteditor from guiqwt.tools import EditItemDataTool self.add_tool(EditItemDataTool) except ImportError: pass self.add_tool(ItemCenterTool) self.add_tool(DeleteItemTool) self.add_separator_tool() t = self.add_tool(RectZoomTool) if activate_zoom: self.set_default_tool(t) self.add_tool(guiqwt_tools.HomeTool) self.add_tool(guiqwt_tools.PanTool) self.add_separator_tool() self.add_tool(BasePlotMenuTool, "grid") self.add_tool(BasePlotMenuTool, "axes") self.add_tool(DisplayCoordsTool) if self.get_itemlist_panel(): self.add_tool(ItemListPanelTool) self.add_separator_tool() self.add_tool(ColormapTool) self.add_tool(ReverseYAxisTool) self.add_tool(AspectRatioTool) if self.get_contrast_panel(): self.add_tool(ContrastPanelTool) if self.get_xcs_panel() and self.get_ycs_panel(): self.add_tool(XCSPanelTool) self.add_tool(YCSPanelTool) self.add_tool(CrossSectionTool) self.add_tool(AverageCrossSectionTool) self.add_separator_tool() self.add_tool(SaveAsTool) self.add_tool(CopyToClipboardTool) self.add_tool(PrintTool) self.add_tool(guiqwt_tools.HelpTool) self.add_separator_tool() self.get_default_tool().activate() def add_option(self, name, change_callback, active=False): """ Add an option (using a checkbox) to the window. :param str name: The name of the option. :param func change_callback: A function accepting the new state as a parameter. The function will be called whenever the state of the option changes. :param bool active: Determines if the option is activated initially. """ checkBox = QtGui.QCheckBox(self) checkBox.setChecked(active) checkBox.setText(name) checkBox.stateChanged.connect(self._option_callback) self.option_callbacks[checkBox] = change_callback self.option_layout.addWidget(checkBox) def add_x_synchronization_option(self, active, ids=None): """ Offer an option for X axes synchronization. This method should be called after show(), so that a proper initial synchronization can be performed. :param bool active: Determines whether the axes are synchronized initially. :param sequence ids: List of plot ids to synchronize. """ self.axis_syncplots[BasePlot.X_BOTTOM] = ids if active and ids: self.synchronize_axis(BasePlot.X_BOTTOM) self.add_option('Synchronize X Axes', PlotDialog._synchronization_option_x, active) def add_y_synchronization_option(self, active, ids=None): """ Offer an option for Y axes synchronization. This method should be called after show(), so that a proper initial synchronization can be performed. :param bool active: Determines whether the axes are synchronized initially :param sequence ids: List of plot ids to synchronize. """ self.axis_syncplots[BasePlot.Y_LEFT] = ids if active and ids: self.synchronize_axis(BasePlot.Y_LEFT) self.add_option('Synchronize Y Axes', PlotDialog._synchronization_option_y, active) def _synchronization_option_x(self, state): """ Callback for x-axis synchronization """ if state: self.synchronize_axis(BasePlot.X_BOTTOM) else: self.unsynchronize_axis(BasePlot.X_BOTTOM) def _synchronization_option_y(self, state): """ Callback for y-axis synchronization """ if state: self.synchronize_axis(BasePlot.Y_LEFT) else: self.unsynchronize_axis(BasePlot.Y_LEFT) def replace_colors(self, replace_list): """ Replace colors of items in all plots. This can be useful when changing the background color to black and black items should be drawn in white: ``replace_colors([('#000000', '#ffffff']))`` :param list replace_list: A list of tuples of Qt color names. The first color in each tuple is replaced by the second color. """ for plot in self.plots.itervalues(): for i in plot.get_items(): if isinstance(i, CurveItem): pen = i.pen() elif isinstance(i, Marker): pen = i.linePen() else: continue for color in replace_list: c1 = QColor(color[0]) c2 = QColor(color[1]) if pen.color() != c1: continue pen.setColor(c2) break if isinstance(i, CurveItem): i.setPen(pen) elif isinstance(i, Marker): i.setLinePen(pen) plot.replot() def set_background_color(self, color): """ Set the background color for all plots. :param str color: A Qt color name (e.g. '#ff0000') """ for p in self.plots.itervalues(): p.setCanvasBackground(QColor(color)) p.replot() def add_unit_color(self, color, name='Unit color:'): """ Create a small legend on top of the window with only one entry. :param str color: A Qt color name (e.g. '#ff0000') :param str name: The name of the legend item. It will be displayed on the left of the color. """ label = QtGui.QLabel(self) label.setText(name) label.setAlignment(Qt.AlignRight) self.color_layout.addWidget(label) label = QtGui.QLabel(self) label.setStyleSheet('background-color:' + str(color)) label.setFrameShape(QFrame.StyledPanel) label.setMaximumWidth(80) self.color_layout.addWidget(label) def add_custom_label(self, legend_string): """ Add a label on the right of the plots :param str legend_string: An arbitrary string (which can contain newlines) to display on the right of the plots """ label = QtGui.QLabel(self) label.setText(legend_string) self.plot_layout.addWidget(label, 0, self.plot_layout.columnCount(), -1, 1) def add_color_legend(self, legend, show_option=None): """ Create a legend on the right of the plots with colors and names. :param sequence legend: List of (color, text) tuples, where `color` is a Qt color name (e.g. '#ff0000') and `text` is the corresponding text to display in the legend. :param bool show_option: Determines whether a toggle for the legend will be shown (if the parameter is not ``None``) and if the legend is visible initially (``True``/``False``). """ widget = QWidget(self) layout = QGridLayout(widget) widget.setLayout(layout) for l in legend: label = QtGui.QLabel(self) label.setStyleSheet('background-color:' + str(l[0])) label.setFrameShape(QFrame.StyledPanel) label.setMaximumWidth(80) label.setMaximumHeight(12) layout.addWidget(label, layout.rowCount(), 0, 1, 1) label = QtGui.QLabel(self) label.setText(l[1]) layout.addWidget(label, layout.rowCount() - 1, 1, 1, 1) self.plot_layout.addWidget(widget, 0, self.plot_layout.columnCount(), -1, 1) if show_option is not None: widget.setVisible(show_option) self.add_option('Show Legend Sidebar', lambda w, s: widget.setVisible(s), show_option) def add_legend_option(self, legends, active): """ Create a user option to show or hide a list of legend objects. :param sequence legends: The legend objects affected by the option. :param bool active: Determines whether the legends will be visible initially. """ self.legends = legends self._set_legend_visibility(active) self.add_option('Show legend', self._legend_callback, active) if active: self._set_legend_visibility(True) def _option_callback(self, state): self.option_callbacks[self.sender()](self, state) #noinspection PyUnusedLocal def _legend_callback(self, win, state): self._set_legend_visibility(state > 0) def _set_legend_visibility(self, visible): for p in self.plots.itervalues(): for l in self.legends: p.set_item_visible(l, visible) def add_plot_widget(self, plot_widget, plot_id, row=-1, column=0, min_plot_width=None, min_plot_height=None): """ Adds a guiqwt plot to the window. :param plot_widget: The plot to add. :type plot_widget: guiqwt plot widget :param int plot_id: The id of the new plot. :param int row: The row of the new plot. If this is -1, the new plot will be added in a new row (if `column` is 0) or in the last row. :param int column: The column of the new plot. :param int min_plot_width: The minimum width of this plot. If ``None``, the default minimum width for this dialog is used. :param int max_plot_height: The minimum height of this plot. If ``None``, the default minimum height for this dialog is used. """ if row == -1: if column == 0: row = self.plot_layout.rowCount() else: row = self.plot_layout.rowCount() - 1 pw = min_plot_width if pw is None: pw = self.min_plot_width ph = min_plot_height if ph is None: ph = self.min_plot_height plot_widget.setMinimumSize(pw, ph) self.plot_layout.addWidget(plot_widget, row, column) new_plot = plot_widget.plot self.add_plot(new_plot, plot_id) def synchronize_axis(self, axis, plots=None): if plots is None: if axis in self.axis_syncplots: plots = self.axis_syncplots[axis] else: plots = self.plots.keys() if len(plots) < 1: return PlotManager.synchronize_axis(self, axis, plots) # Find interval that needs to be shown in order to include all # currently shown parts in the synchronized plots plot_objects = [self.plots[p] for p in plots] lb = min((p.axisScaleDiv(axis).lowerBound() for p in plot_objects)) ub = max((p.axisScaleDiv(axis).upperBound() for p in plot_objects)) for p in plot_objects: p.setAxisScale(axis, lb, ub) p.replot() def unsynchronize_axis(self, axis, plots=None): if plots is None: if axis in self.axis_syncplots: plots = self.axis_syncplots[axis] else: plots = self.plots.keys() for plot_id in plots: if not plot_id in self.synchronized_plots: continue synclist = self.synchronized_plots[plot_id] for plot2_id in plots: if plot_id == plot2_id: continue item = (axis, plot2_id) if item in synclist: synclist.remove(item) def plot_axis_changed(self, plot): ids = [k for k, p in self.plots.iteritems() if p == plot] if len(ids) < 1: return plot_id = ids[0] if plot_id not in self.synchronized_plots: return for (axis, other_plot_id) in self.synchronized_plots[plot_id]: scalediv = plot.axisScaleDiv(axis) other = self.get_plot(other_plot_id) lb = scalediv.lowerBound() ub = scalediv.upperBound() other.setAxisScale(axis, lb, ub) other.replot() def set_plot_title(self, plot, title): """ Set the title of a guiqwt plot and use the same font as for the rest of the window. :param plot: The plot for which the title is set. :param str title: The new title of the plot. """ plot.setTitle(title) l = plot.titleLabel() l.setFont(self.font()) plot.setTitle(l.text()) def show(self): for p in self.plots.itervalues(): if not self.minor_grid: p.grid.gridparam.min_xenabled = False p.grid.gridparam.min_yenabled = False if not self.major_grid: p.grid.gridparam.maj_xenabled = False p.grid.gridparam.maj_yenabled = False p.grid.update_params() super(PlotDialog, self).show()
class Hakkinda(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.resize(500, 350) self.setMaximumSize(500, 350) self.gLayout = QGridLayout(self) self.logo = QLabel(self) self.logo.setPixmap(QPixmap(":/resim/parcala.png")) self.gLayout.addWidget(self.logo, 0, 0, 2, 1) self.appName = QLabel(self) font = QFont() font.setPointSize(32) font.setWeight(50) self.appName.setFont(font) self.gLayout.addWidget(self.appName, 0, 1, 1, 2) self.appVersion = QLabel(self) font = QFont() font.setPointSize(9) font.setWeight(75) font.setBold(True) self.appVersion.setFont(font) self.appVersion.setAlignment(Qt.AlignHCenter|Qt.AlignTop) self.gLayout.addWidget(self.appVersion, 1, 1, 1, 2) self.gBox = QGroupBox(self) font = QFont() font.setPointSize(12) font.setWeight(75) font.setBold(True) self.gBox.setFont(font) self.gLayout2 = QGridLayout(self.gBox) self.scrollArea = QScrollArea(self.gBox) self.scrollArea.setFrameShape(QFrame.NoFrame) self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents.setGeometry(0, 0, 476, 199) self.gLayout3 = QGridLayout(self.scrollAreaWidgetContents) self.appHakkinda = QLabel(self.scrollAreaWidgetContents) font = QFont() font.setPointSize(9) font.setWeight(50) font.setBold(False) self.appHakkinda.setFont(font) self.appHakkinda.setWordWrap(True) self.gLayout3.addWidget(self.appHakkinda, 0, 0, 1, 1) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.gLayout2.addWidget(self.scrollArea, 0, 0, 1, 1) self.gLayout.addWidget(self.gBox, 2, 0, 2, 4) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.gLayout.addItem(spacerItem, 0, 3, 1, 1) spacerItem1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.gLayout.addItem(spacerItem1, 2, 1, 1, 2) self.setWindowTitle(self.trUtf8(u"%s Hakkında"%QApplication.applicationName())) self.appName.setText(self.trUtf8(u"%s"%QApplication.applicationName())) self.appVersion.setText(self.trUtf8(u"Sürüm %s"%QApplication.applicationVersion())) self.gBox.setTitle(self.trUtf8("Hakkında")) self.appHakkinda.setText(self.trUtf8(""" <p>Parçala, Hj-Split ile aynı işi yapan dosya parçalama ve birleştirme yazılımıdır.</p> <p>Parçala, büyük boyutlu dosyaları parçalara böldüğü gibi parçalanmış dosyaları birleştirme ve dosyaların doğrulamasını yapabilmektedir.</p> <p><b>Geliştirici:</b> Metehan Özbek - <a href='mailto:[email protected]'>[email protected]</a></p> <p><b>Görsel Çalışma:</b> Yasin Özcan - <a href='mailto:[email protected]'>[email protected]</a></p> <p><b>Lisans:</b> GPL v3</p> <p></p>"""))