class Action: def __init__(self, code, label, funktion, roles): self.code = code self.label = label self._function = funktion self._roles = roles def is_enabled(self): if self._roles is not None: return user_session.has_any_roles(self._roles) else: return True def as_QAction(self, parent): self._qaction = QAction(self.label, parent) self._qaction.triggered.connect(self._function) # self._qaction.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_PageUp)) self._qaction.setShortcutContext(Qt.WidgetWithChildrenShortcut)
def populate_menu( menu, action_target, list_actions, context=Qt.WindowShortcut): """ action_target is a QWidget where the menu's action can be added so that they are callable from there """ global configuration global user_session for t in list_actions: # t = (Text, Slot to call, KeySequence shortcut, roles which can use the action) # t = (QAction, roles which can use the action) # if QAction then slot, KeySequence will be ignored roles = a = None if isinstance( t[0], QAction): a = t[0] roles = t[1] if a.parent() is None: action_target.addAction(a) elif isinstance( t[0], QMenu): a = t[0] roles = t[1] else: a = QAction(t[0],action_target) # text, parent if t[1]: a.triggered.connect(t[1]) if t[2]: a.setShortcut(t[2]) a.setShortcutContext(context) roles = t[3] action_target.addAction(a) if roles is not None: a.setEnabled(user_session.has_any_roles(roles)) if isinstance( a, QMenu): menu.addMenu(a) else: menu.addAction(a)
class EditSupplyOrderPanel(HorsePanel): supply_order_saved = Signal() def panel_content_hash(self): return (self.current_supply_order_id, self.current_supplier.supplier_id) def needs_close_confirmation(self): return self.model_data_changed def confirm_close(self): return self._save_if_necessary() @Slot() def change_supplier(self): from koi.supply.ChooseSupplierDialog import ChooseSupplierDialog from PySide.QtGui import QDialog d = ChooseSupplierDialog(self) if d.exec_() and d.result( ) == QDialog.Accepted and d.supplier_id != self.current_supplier.supplier_id: mainlog.debug("Supplier chosen {}".format(d.supplier)) self.current_supplier = d.supplier self.supplier_plate_widget.set_contact_data(self.current_supplier) self.data_changed2_slot() @Slot() def print_supply_order(self): if self._save_if_necessary(): try: print_supply_order(self.current_supply_order_id) except Exception as e: showErrorBox(str(e)) def _save_if_necessary(self): """ True if the user has either said he doesn't want to save or he saved successufly. False if the user has cancelled (no save, no "no save") or the save operation has failed """ if self.model_data_changed: ynb = yesNoBox( _("Data were changed"), _("You have changed some of the data in this. Do you want to save before proceeding ?" )) if ynb == QMessageBox.Yes: if self.save( ) != False: # FIXME defensive, make sure it's True # Because when I save, although the order definition is # not meant to change, the order numbers (accounting label, # etc.) might actually change. self.reload() return True else: return False elif ynb == QMessageBox.No: return True elif ynb == QMessageBox.Cancel: return False else: return True def edit_new(self, supplier): """ Start editing a brand new order """ self.current_supplier = supplier self.current_supply_order_id = None self.edit_comment_widget.setText(None) self.delivery_date_widget.set_value(None) self.creation_date_widget.setText("") self.controller_part.model.clear() self.controller_part.model.insertRows(0, 1, QModelIndex()) self.accounting_label = None self._set_view(None, True) mainlog.debug("edit_new : supplier : {}".format( self.current_supplier.supplier_id)) def edit(self, supply_order_id): """ Start editing an existing order """ self._fill_view_with_order_data(supply_order_id) self._set_view(None, False) def reload(self): ndx = self.controller_part.view.currentIndex() self._fill_view_with_order_data(self.current_supply_order_id) self._set_view(ndx, False) def _fill_view_with_order_data(self, order_id): sorder, sorder_parts = supply_order_service.find_by_id(order_id) self.edit_comment_widget.setText(sorder.description) self.delivery_date_widget.set_value(sorder.expected_delivery_date) self.creation_date_widget.setText(date_to_dmy(sorder.creation_date)) self.supplier_reference_widget.setText(sorder.supplier_reference) self.current_supplier = supplier_service.find_by_id(sorder.supplier_id) self.current_supply_order_id = sorder.supply_order_id self.model._buildModelFromObjects(sorder_parts) self.accounting_label = sorder.accounting_label def _set_view(self, ndx=None, start_editing=False): """ Once the data and models are prepared, one comes here to start the editing... """ self.supplier_plate_widget.set_contact_data(self.current_supplier) self.controller_part.view.setFocus() if not ndx: ndx = self.controller_part.model.index(0, 0) # Somehow, editing a cell doesn't set up the currentIndex automatically... self.controller_part.view.setCurrentIndex(ndx) if start_editing: self.controller_part.view.edit(ndx) self.model_data_changed = False self.title_widget.set_modified_flag(False) ref = self.accounting_label or self.supplier_reference_widget.text() if ref: self.set_panel_title(u"{}\n{}".format( ref, self.current_supplier.fullname)) else: self.set_panel_title( _("Supply order") + u"\n" + self.current_supplier.fullname) refs = [] if self.accounting_label: refs.append( u"<span style='color:orange; font-style:normal;'>{}</span>". format(self.accounting_label)) if self.supplier_reference_widget.text(): refs.append( u"<span style='color:black; font-style:normal;'>{}</span>". format(self.supplier_reference_widget.text())) self.in_title_label.setText( _("<h1>Ref. : {}</h1>").format(u" / ".join(refs))) def _validate(self): errs = self.model.validate() if errs: errs = [formatErrorsOnLine(errs)] else: errs = [] if not self.delivery_date_widget.is_valid(): errs.append(_('The date is not valid')) return errs or True def save(self): if self.model_data_changed: focused_widget = QApplication.focusWidget() if isinstance(focused_widget, QLineEdit) and focused_widget.parent() != self: focused_widget.clearFocus() focused_widget = QApplication.focusWidget() validation_results = self._validate() if validation_results != True: showErrorBox(_("The data are not correct"), "<br>-" + "<br>-".join(validation_results)) return False mainlog.debug("save : supplier_id : {}".format( self.current_supplier.supplier_id)) obj = blank_dto(SupplyOrder) obj.supply_order_id = self.current_supply_order_id obj.creation_date = date.today() obj.description = self.edit_comment_widget.toPlainText() obj.supplier_id = self.current_supplier.supplier_id obj.expected_delivery_date = self.delivery_date_widget.value() obj.supplier_reference = self.supplier_reference_widget.text() obj.accounting_label = self.accounting_label def factory(): return blank_dto(SupplyOrderPart) parts_actions = self.model.model_to_objects(factory) try: mainlog.debug("Saving") self.current_supply_order_id = supply_order_service.save( obj, parts_actions) # Reload the data self.reload() mainlog.debug("Emitting change signal") self.supply_order_saved.emit() # self.current_supply_order_id) except Exception as ex: showErrorBox( _("Error while saving"), _("There was an unexpected error while saving your data"), ex, object_name="saveError") if focused_widget: focused_widget.setFocus(Qt.OtherFocusReason) @Slot() def delete(self): if self.current_supply_order_id: s = _("About to delete order {}").format(self.accounting_label) if confirmationBox(s, _("Are you sure ?")): try: supply_order_service.deactivate( self.current_supply_order_id) except Exception as ex: showErrorBox("I could not delete the order", ex=ex) return rep = supply_order_service.find_last_order_id( self.current_supplier.supplier_id) if rep: self.edit(rep) else: self.edit_new(self.current_supplier) else: showErrorBox( _("No order or non-saved order selected"), _("You can only delete an order that has already been saved"), object_name="delete_only_saved_order") return @Slot() def show_actions(self): button = self.action_menu.parent() p = button.mapToGlobal(QPoint(0, button.height())) self.action_menu.exec_(p) @Slot(QStandardItem) def data_changed_slot(self, item): self.model_data_changed = True self.title_widget.set_modified_flag(self.model_data_changed) @Slot() def data_changed2_slot(self): self.model_data_changed = True self.title_widget.set_modified_flag(self.model_data_changed) @Slot() def next_order_for_supplier(self): if self._save_if_necessary(): # This could find nothing if there's no order for the supplier :-) oid = supply_order_service.find_next_for_supplier( self.current_supply_order_id, self.current_supplier.supplier_id) if oid: self.edit(oid) @Slot() def previous_order_for_supplier(self): if self._save_if_necessary(): oid = supply_order_service.find_previous_for_supplier( self.current_supply_order_id, self.current_supplier.supplier_id) if oid: self.edit(oid) def __init__(self, parent): super(EditSupplyOrderPanel, self).__init__(parent) self.current_supply_order_id = None self.proto = [] self.proto.append( TextAreaPrototype('description', _('Description'), editable=True, nullable=False)) self.proto.append( FloatNumberPrototype('quantity', _('Quantity'), editable=True, nullable=False)) self.proto.append( FloatNumberPrototype('unit_price', _('Unit price'), editable=True, nullable=True)) self.delivery_date_prototype = FutureDatePrototype('description', ('Description'), editable=True, nullable=False) self.model = SupplyOrderPartsModel(self, self.proto) self.controller_part = PrototypeController( self, self.proto, ProxyTableView(None, self.proto)) self.controller_part.setModel(self.model) # self.controller_part.view.verticalHeader().hide() self.controller_part.view.horizontalHeader().setResizeMode( 0, QHeaderView.Stretch) self.print_supply_order_action = QAction(_("Print supply order"), self) # , parent self.print_supply_order_action.triggered.connect( self.print_supply_order) self.print_supply_order_action.setShortcut( QKeySequence(Qt.CTRL + Qt.Key_P)) self.print_supply_order_action.setShortcutContext( Qt.WidgetWithChildrenShortcut) self.addAction(self.print_supply_order_action) self.save_supply_order_action = QAction(_("Save supply order"), self) # , parent self.save_supply_order_action.triggered.connect(self.save) self.save_supply_order_action.setShortcut( QKeySequence(Qt.CTRL + Qt.Key_S)) self.save_supply_order_action.setShortcutContext( Qt.WidgetWithChildrenShortcut) self.addAction(self.save_supply_order_action) self.delete_supply_order_action = QAction(_("Deactivate supply order"), self) # , parent self.delete_supply_order_action.triggered.connect(self.delete) self.addAction(self.delete_supply_order_action) self.next_order_for_supplier_action = QAction( _("Next supplier's order"), self) # , parent self.next_order_for_supplier_action.setShortcut( QKeySequence(Qt.CTRL + Qt.Key_PageDown)) self.next_order_for_supplier_action.setShortcutContext( Qt.WidgetWithChildrenShortcut) self.next_order_for_supplier_action.triggered.connect( self.next_order_for_supplier) self.addAction(self.next_order_for_supplier_action) self.previous_order_for_supplier_action = QAction( _("Previous supplier's order"), self) # , parent self.previous_order_for_supplier_action.setShortcut( QKeySequence(Qt.CTRL + Qt.Key_PageDown)) self.previous_order_for_supplier_action.setShortcutContext( Qt.WidgetWithChildrenShortcut) self.previous_order_for_supplier_action.triggered.connect( self.next_order_for_supplier) self.addAction(self.previous_order_for_supplier_action) self.change_supplier_action = QAction(_("Change supplier"), self) # , parent self.change_supplier_action.triggered.connect(self.change_supplier) self.addAction(self.change_supplier_action) # self.controller_operation.view.addAction(self.reprint_delivery_slip) self.controller_part.view.addAction(self.print_supply_order_action) self.controller_part.view.addAction(self.save_supply_order_action) self.controller_part.view.addAction(self.delete_supply_order_action) navigation = NavBar(self, [(self.next_order_for_supplier_action.text(), self.next_order_for_supplier), (self.previous_order_for_supplier_action.text(), self.previous_order_for_supplier), (_("Action"), self.show_actions)]) navigation.buttons[2].setObjectName("specialMenuButton") self.action_menu = QMenu(navigation.buttons[2]) list_actions = [(self.print_supply_order_action, None), (self.save_supply_order_action, None), (self.delete_supply_order_action, None), (self.change_supplier_action, None)] populate_menu(self.action_menu, self, list_actions, context=Qt.WidgetWithChildrenShortcut) self.title_widget = TitleWidget(_("Supply order"), self, navigation) self.supplier_plate_widget = SupplierPlateWidget(self) self.edit_comment_widget = DescribedTextEdit(_("Comments")) self.edit_comment_widget.setMinimumHeight(20) self.edit_comment_widget.setMinimumWidth(600) self.edit_comment_widget.setMaximumHeight(60) self.edit_comment_widget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.supplier_reference_widget = QLineEdit() self.delivery_date_widget = DateEntryWidget() self.delivery_date_widget.setMaximumWidth(100) self.creation_date_widget = QLabel() self.in_title_label = QLabel() top_layout1 = QHBoxLayout() top_layout1.addWidget(self.in_title_label) top_layout1.addStretch() top_layout1.addWidget(self.supplier_plate_widget) hlayout = QHBoxLayout() hlayout.addWidget(QLabel(_("Delivery date"))) hlayout.addWidget(self.delivery_date_widget) hlayout.addStretch() hlayout3 = QHBoxLayout() hlayout3.addWidget(QLabel(_("Creation date"))) hlayout3.addWidget(self.creation_date_widget) hlayout3.addStretch() hlayout2 = QHBoxLayout() hlayout2.addWidget(QLabel(_("Supplier's reference"))) hlayout2.addWidget(self.supplier_reference_widget) hlayout2.addStretch() vlayout = QVBoxLayout() vlayout.addLayout(hlayout) # delivery date vlayout.addLayout(hlayout3) # creation date vlayout.addLayout(hlayout2) # reference vlayout.addStretch() top_layout2 = QHBoxLayout() top_layout2.addLayout(vlayout) top_layout2.addWidget(self.edit_comment_widget) top_layout2.addStretch() top_layout2.setStretch(0, 0) top_layout2.setStretch(1, 0) # For some reason, the stretch added above is not enough # to push the whole layout to the left. I have to set # it's stretch factor too... top_layout2.setStretch(2, 100) vhead_layout = QVBoxLayout() vhead_layout.addWidget(self.title_widget) top_layout1.setContentsMargins(4, 0, 4, 0) # vhead_layout.addLayout(top_layout1) vhead_layout.addWidget(InlineSubFrame(top_layout1, None)) vhead_layout.addLayout(top_layout2) # top_layout2.setContentsMargins(4,4,4,4) #vhead_layout.addWidget(InlineSubFrame(top_layout2,None)) vhead_layout.addWidget(self.controller_part.view) vhead_layout.setStretch(0, 0) vhead_layout.setStretch(1, 0) vhead_layout.setStretch(2, 0) vhead_layout.setStretch(3, 10) self.setLayout(vhead_layout) self.controller_part.view.enable_edit_panel() # Handling changes in the model (helpful to know if saving # is necessary) self.model_data_changed = False self.model.rowsInserted.connect(self.data_changed_slot) self.model.rowsRemoved.connect(self.data_changed_slot) self.model.dataChanged.connect(self.data_changed_slot) self.supplier_reference_widget.textChanged.connect( self.data_changed2_slot) self.edit_comment_widget.textChanged.connect(self.data_changed2_slot) self.delivery_date_widget.textChanged.connect(self.data_changed2_slot)
class DeliverySlipPanel(HorsePanel): delivery_slip_changed = Signal() def close_panel(self): self.filter_widget.remember_current_selection( configuration) def _selected_slip(self): cur_ndx = self.search_results_view.currentIndex() if cur_ndx.row() >= 0: return cur_ndx.model().object_at(cur_ndx.row()) return None def reprint(self): cur_ndx = self.search_results_view.currentIndex() slip_id = cur_ndx.model().index(cur_ndx.row(),0).data() if dao.delivery_slip_part_dao.id_exists(slip_id): print_delivery_slip(dao,slip_id) else: makeErrorBox(_("The delivery slip {} doesn't exist").format(slip_id)).exec_() def desactivate(self): slip_id = self._selected_slip().delivery_slip_id mainlog.debug("Desactivate slip {}".format(slip_id)) dao.delivery_slip_part_dao.deactivate(slip_id) self.refresh(slip_id) self.delivery_slip_changed.emit() def activate(self): slip_id = self._selected_slip().delivery_slip_id dao.delivery_slip_part_dao.activate(slip_id) self.refresh(slip_id) self.delivery_slip_changed.emit() def delete(self): slip_id = self._selected_slip().delivery_slip_id dao.delivery_slip_part_dao.delete_last(slip_id) self.refresh(slip_id) self.delivery_slip_changed.emit() @Slot() def show_actions(self): button = self.action_menu.parent() p = button.mapToGlobal(QPoint(0,button.height())) self.action_menu.exec_(p) @Slot() def _toggle_edit_filters(self): self.filter_widget.setVisible( not self.filter_widget.isVisible()) def __init__(self,parent): super(DeliverySlipPanel,self).__init__(parent) title = _("Delivery slips") self.slip_data = None self.set_panel_title(_("Delivery slip overview")) self.reprint_delivery_slip = QAction(_("Reprint delivery slip"),self) # , parent self.reprint_delivery_slip.triggered.connect( self.reprint) # self.reprint_delivery_slip.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_V)) self.reprint_delivery_slip.setShortcutContext(Qt.WidgetWithChildrenShortcut) # self.controller_operation.view.addAction(self.reprint_delivery_slip) self.desactivate_delivery_slip = QAction(_("Desactivate delivery slip"),self) # , parent self.desactivate_delivery_slip.triggered.connect( self.desactivate) self.desactivate_delivery_slip.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.activate_delivery_slip = QAction(_("Activate delivery slip"),self) # , parent self.activate_delivery_slip.triggered.connect( self.activate) self.activate_delivery_slip.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.delete_delivery_slip = QAction(_("Delete delivery slip"),self) # , parent self.delete_delivery_slip.triggered.connect( self.delete) self.delete_delivery_slip.setShortcutContext(Qt.WidgetWithChildrenShortcut) filter_family = FilterQuery.DELIVERY_SLIPS_FAMILY self.filter_widget = PersistentFilter( filter_family, suggestion_finder) self.filter_widget.apply_filter.connect(self.apply_filter_slot) self.filter_widget.hide() navigation = NavBar( self, [ (self.filter_widget.get_filters_combo(), None), (_("Edit filter"),self._toggle_edit_filters), (_("Action"),self.show_actions) ] ) self.title_widget = TitleWidget(title, self, navigation) # navigation) self.action_menu = QMenu(navigation.buttons[0]) list_actions = [ (self.reprint_delivery_slip,None), (self.activate_delivery_slip,None), (self.desactivate_delivery_slip,None), # (self.delete_delivery_slip,None) ] populate_menu(self.action_menu, self, list_actions, context=Qt.WidgetWithChildrenShortcut) # self.setWindowTitle(title) top_layout = QVBoxLayout(self) # self.filter_line_edit = QLineEdit() # self.filter_line_edit = QueryLineEdit(suggestion_finder) initialize_customer_cache() # FIXME Not the place to do that # filter_family = 'delivery_slips' # self.filter_name = FiltersCombo(self, filter_family) # filter_widget = PersistentFilter(filter_family, self.filter_name) # filter_widget.apply_filter.connect(self.apply_filter_slot) self.proto = [] self.proto.append( IntegerNumberPrototype('delivery_slip_id',_("Slip Nr"), editable=False)) self.proto.append( DatePrototype('creation',_('Date'), editable=False)) self.proto.append( TextLinePrototype('fullname',_('Customer'), editable=False)) self.proto.append( TextLinePrototype('user_label',_('Order'), editable=False)) self.search_results_model = DeliverySlipPanelModel(self.proto, self) self.search_results_view = PrototypedQuickView(self.proto, self) self.search_results_view.setModel(self.search_results_model) self.search_results_view.verticalHeader().hide() self.search_results_view.horizontalHeader().setResizeMode(1, QHeaderView.ResizeToContents) self.search_results_view.horizontalHeader().setResizeMode(2, QHeaderView.Stretch) self.search_results_view.horizontalHeader().setSortIndicatorShown(True) self.search_results_view.horizontalHeader().setSortIndicator(0,Qt.AscendingOrder) self.search_results_view.horizontalHeader().sectionClicked.connect(self._section_clicked) self.search_results_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.search_results_view.setSelectionMode(QAbstractItemView.SingleSelection) self.search_results_view.selectionModel().currentRowChanged.connect(self.row_selected) self.slip_part_view = DeliverySlipViewWidget(self) hlayout_results = QHBoxLayout() # w = SubFrame(_("Delivery slips"),self.search_results_view,None) hlayout_results.addWidget(self.search_results_view) w = SubFrame(_("Detail"),self.slip_part_view,None) hlayout_results.addWidget(w) top_layout.addWidget(self.title_widget) top_layout.addWidget(self.filter_widget) top_layout.addLayout(hlayout_results) top_layout.setStretch(2,100) self.setLayout(top_layout) self.filter_widget.load_last_filter( configuration) def refresh(self,slip_id): filter_string = self.filter_line_edit.text() self.apply_filter_slot(filter_string) if slip_id: ndx = self.search_results_model.find_index( lambda s:s.delivery_slip_id == slip_id) r = 0 m = self.search_results_model if ndx: r = ndx.row() s = QItemSelection(m.index(r,0), m.index(r, m.columnCount()-1)) self.search_results_view.selectionModel().select(s, QItemSelectionModel.Select) @Slot(int) def _section_clicked(self,logical_ndx): self.refresh_action() @Slot(str) def apply_filter_slot(self, filter_string): if filter_string: try: self.slip_data = dao.delivery_slip_part_dao.load_slip_parts_on_filter(filter_string) except DataException as de: if de.code == DataException.CRITERIA_IS_EMPTY: showErrorBox(_("Error in the filter !"),_("The filter can't be empty"),object_name="filter_is_empty") elif de.code == DataException.CRITERIA_IS_TOO_SHORT: showErrorBox(_("Error in the filter !"),_("The filter is too short"),object_name="filter_is_too_short") elif de.code == DataException.CRITERIA_IS_TOO_LONG: showErrorBox(_("Error in the filter !"),_("The filter is too long"),object_name="filter_is_too_long") return else: self.slip_data = dao.delivery_slip_part_dao.find_recent2(1000) self.refresh_action() def refresh_action(self): if self.slip_data == None: self.slip_data = dao.delivery_slip_part_dao.find_recent2(1000) data = self.slip_data headers = self.search_results_view.horizontalHeader() section_sorted = headers.sortIndicatorSection() sort_criteria = self._get_sort_criteria( section_sorted) sort_order = None if sort_criteria: sort_order = headers.sortIndicatorOrder() data = sorted(data, key=cmp_to_key(sort_criteria), reverse = sort_order == Qt.DescendingOrder) self.search_results_model.buildModelFromObjects(data) if sort_criteria: # Changing the model removes the sort order (which makes sense because # changing the model may alter the order of rows) headers.setSortIndicator(section_sorted,sort_order) self.search_results_view.setFocus(Qt.OtherFocusReason) @Slot(QModelIndex,QModelIndex) def row_selected(self,cur_ndx,prev_ndx): if cur_ndx.model(): slip = cur_ndx.model().object_at( cur_ndx.row()) if slip: slip_id = slip.delivery_slip_id self.slip_part_view.set_delivery_slip_parts(dao.delivery_slip_part_dao.load_slip_parts_frozen(slip_id)) self.activate_delivery_slip.setEnabled( not slip.active) self.desactivate_delivery_slip.setEnabled( slip.active) self.delete_delivery_slip.setEnabled( cur_ndx.row() == 0) return self.slip_part_view.set_delivery_slip_parts(None) SECTION_SLIP_ID = 0 SECTION_CREATION_DATE = 1 SECTION_CUSTOMER = 2 SECTION_ORDER_LABEL = 3 def _get_sort_criteria(self,section_sorted): sort_criteria = None # True if the parts of the order stay grouped # together in the table order_stay_together = True if section_sorted == self.SECTION_CUSTOMER: # Customer sort_criteria = lambda a,b: cmp(a.fullname, b.fullname) or \ cmp(a.delivery_slip_id,b.delivery_slip_id) elif section_sorted == self.SECTION_CREATION_DATE: sort_criteria = lambda a,b: cmp(a.creation or date(2100,1,1), b.creation or date(2100,1,1)) elif section_sorted == self.SECTION_SLIP_ID: sort_criteria = lambda a,b: cmp(a.delivery_slip_id, b.delivery_slip_id) elif section_sorted == self.SECTION_ORDER_LABEL: sort_criteria = lambda a,b: cmp(a.user_label, b.user_label) else: sort_criteria = None if sort_criteria is not None: self.current_sort_criteria = sort_criteria else: sort_criteria = self.current_sort_criteria return sort_criteria
class PresenceOverviewWidget(HorsePanel): @Slot(QModelIndex) def cell_entered(self, ndx): employee_id = self._employee_id_on_row(ndx) if not employee_id: self._show_totals_day_off(None) elif employee_id: chrono.chrono_start() employee = None for i in self.employees: if i.employee_id == employee_id: employee = i break self.detail_subframe.set_title(employee.fullname) self._show_totals_day_off(employee_id) d = date( self.base_date.year, self.base_date.month, min( calendar.monthrange(self.base_date.year, self.base_date.month)[1], max(1, ndx.column()))) chrono.chrono_click("Retrieveing data") tars = dao.task_action_report_dao.get_reports_for_employee_id_on_date( employee_id, d) work_timetracks = dao.timetrack_dao.all_work_for_employee_date_manual( employee_id, d) presence_timetracks = dao.timetrack_dao.all_presence_for_employee_date_managed_by_code_full( employee_id, d) # employee = dao.employee_dao.find_by_id(employee_id) special_activities = dao.special_activity_dao.find_on_day( employee_id, d) chrono.chrono_click("Redrawing") self.time_report_view.redraw(datetime(d.year, d.month, d.day, 6, 0), tars, employee_id, work_timetracks, presence_timetracks, special_activities, view_title=_("Work on {}").format( date_to_dmy(d, full_month=True))) session().close( ) # FIXME Put his one line above; but that's tough ! SQLA doesn't help us much here ! chrono.chrono_click("Session closed") # for event_type, duration in self.day_event_totals[employee_id].items(): # mainlog.debug("{}{}".format(event_type, duration)) self._toggle_days_off_actions() def _employee_id_on_row(self, row_or_ndx): r = row_or_ndx if type(r) != int: r = row_or_ndx.row() return self._table_model.data(self._table_model.index(r, 0), Qt.UserRole) DAY_EVENT_PALETTE = { DayEventType.holidays: (Qt.GlobalColor.white, Qt.GlobalColor.red), DayEventType.day_off: (Qt.GlobalColor.white, Qt.GlobalColor.darkRed), DayEventType.unpaid_day_off: (Qt.GlobalColor.black, Qt.GlobalColor.magenta), DayEventType.free_day: (Qt.GlobalColor.white, Qt.GlobalColor.darkMagenta), DayEventType.overtime: (Qt.GlobalColor.black, Qt.GlobalColor.green), DayEventType.recuperation: (Qt.GlobalColor.white, Qt.GlobalColor.darkGreen), DayEventType.unemployment: (Qt.GlobalColor.white, Qt.GlobalColor.blue), DayEventType.unemployment_short: (Qt.GlobalColor.white, Qt.GlobalColor.darkBlue), DayEventType.work_accident: (Qt.GlobalColor.black, Qt.GlobalColor.yellow), DayEventType.sick_leave: (Qt.GlobalColor.white, Qt.GlobalColor.darkYellow) } MONTH_EVENT_COLUMN = 2 YEAR_EVENT_COLUMN = 3 def _make_total_days_off_panel(self): widget = QFrame() widget.setObjectName('HorseRegularFrame') widget.setFrameShape(QFrame.Panel) widget.setFrameShadow(QFrame.Sunken) layout = QVBoxLayout() #layout.addWidget(QLabel(_("Days off to date"))) self.day_off_total_duration_labels = dict() self.day_off_month_duration_labels = dict() self.day_off_labels = dict() self._day_off_table_model = QStandardItemModel(10, 3) self._day_off_table_model.setHorizontalHeaderLabels( [None, None, _("This\nmonth"), _("Before")]) self.day_off_table_view = QTableView(None) self.day_off_table_view.setModel(self._day_off_table_model) # self.day_off_table_view.setHorizontalHeader(self.headers_view) self.day_off_table_view.verticalHeader().hide() self.day_off_table_view.setAlternatingRowColors(True) self.day_off_table_view.setEditTriggers( QAbstractItemView.NoEditTriggers) self.day_off_table_view.hide() row = 0 for det in DayEventType.symbols(): ndx = self._day_off_table_model.index(row, 0) self._day_off_table_model.setData(ndx, det.description, Qt.DisplayRole) ndx = self._day_off_table_model.index(row, 1) fg, bg = self.DAY_EVENT_PALETTE[det] self._day_off_table_model.setData(ndx, QBrush(bg), Qt.BackgroundRole) self._day_off_table_model.setData(ndx, QBrush(fg), Qt.TextColorRole) self._day_off_table_model.setData(ndx, DayEventType.short_code(det), Qt.DisplayRole) row += 1 layout.addWidget(self.day_off_table_view) grid = QGridLayout() self.days_off_layout = grid grid.setColumnStretch(3, 1) row = 0 grid.addWidget(QLabel(_('Year')), row, self.YEAR_EVENT_COLUMN) grid.addWidget(QLabel(_('Month')), row, self.MONTH_EVENT_COLUMN) row += 1 for det in DayEventType.symbols(): self.day_off_total_duration_labels[det] = QLabel("-") self.day_off_month_duration_labels[det] = QLabel("-") self.day_off_labels[det] = QLabel(det.description) hlayout = QHBoxLayout() sl = QLabel() fg, bg = self.DAY_EVENT_PALETTE[det] def to_html_rgb(color): i = color.red() * 256 * 256 + color.green() * 256 + color.blue( ) return "#{:06X}".format(i) p = QPalette() p.setColor(QPalette.Window, QColor(bg)) p.setColor(QPalette.WindowText, QColor(fg)) sl.setPalette(p) sl.setAlignment(Qt.AlignCenter) sl.setStyleSheet("border: 2px solid black; background: {}".format( to_html_rgb(QColor(bg)))) t = DayEventType.short_code(det) mainlog.debug(t) sl.setAutoFillBackground(True) sl.setText(t) grid.addWidget(sl, row, 0) grid.addWidget(self.day_off_labels[det], row, 1) grid.addWidget(self.day_off_total_duration_labels[det], row, self.YEAR_EVENT_COLUMN) grid.addWidget(self.day_off_month_duration_labels[det], row, self.MONTH_EVENT_COLUMN) hlayout.addStretch() row += 1 layout.addLayout(grid) layout.addStretch() self.day_off_table_view.resizeColumnsToContents() # self.day_off_table_view.setMinimumWidth( self.day_off_table_view.width()) # self.day_off_table_view.resize( self.day_off_table_view.minimumWidth(), # self.day_off_table_view.minimumHeight(),) widget.setLayout(layout) return widget def _show_totals_day_off(self, employee_id): mainlog.debug("_show_totals_day_off : {}".format(employee_id)) def form_layout_row_set_visible(layout, row_ndx, is_visible): for i in range(layout.columnCount()): l = layout.itemAtPosition(row_ndx, i) if l and l.widget(): l.widget().setVisible(is_visible) det_to_show = dict() row = 0 for det in DayEventType.symbols(): yearly = 0 if employee_id in self.all_events_in_year: if det in self.all_events_in_year[employee_id]: yearly = nice_round( self.all_events_in_year[employee_id][det]) monthly = 0 if employee_id in self.day_event_totals: if det in self.day_event_totals[employee_id]: monthly = nice_round( self.day_event_totals[employee_id][det]) # ndx = self._day_off_table_model.index(row,self.YEAR_EVENT_COLUMN) # self._day_off_table_model.setData(ndx, v, Qt.DisplayRole) if yearly or monthly: det_to_show[det] = {'monthly': monthly, 'yearly': yearly} if det_to_show: # If there are some days spent on some counters, then we display # those counters *only* mainlog.debug("_show_totals_day_off : showing some events ".format( str(det_to_show))) row = 0 for det in DayEventType.symbols(): if det in det_to_show: monthly = det_to_show[det]['monthly'] yearly = det_to_show[det]['yearly'] form_layout_row_set_visible(self.days_off_layout, row + 1, True) self.day_off_total_duration_labels[det].setText(yearly or '-') self.day_off_month_duration_labels[det].setText(monthly or '-') else: form_layout_row_set_visible(self.days_off_layout, row + 1, False) else: # If there are no days spent on any counter, then we display # all counters at the 0 mark. mainlog.debug("_show_totals_day_off : showing no event") row = 0 for det in DayEventType.symbols(): form_layout_row_set_visible(self.days_off_layout, row + 1, True) row += 1 # self.day_off_table_view.resizeColumnsToContents() self.days_off_panel.parent().update() @Slot() def refresh_action(self): global dao # mainlog.debug("refresh action started") self.hours_per_pers_subframe.set_title(date_to_my( self.base_date, True)) chrono.chrono_start() all_events_in_month = people_admin_service.events_for_month( self.base_date) employee_with_events = [ event.employee_id for event in all_events_in_month ] # mainlog.debug(all_events_in_month) self.all_events_in_year = people_admin_service.events_for_year( self.base_date.year) self.all_presences = all_presences = dao.employee_dao.presence_overview_for_month( self.base_date) all_correction_times = dict() for s in dao.month_time_synthesis_dao.load_all_synthesis( self.base_date.year, self.base_date.month): all_correction_times[s.employee_id] = s.correction_time special_activities = dao.special_activity_dao.find_on_month( self.base_date) employees = list( filter( lambda e: e.is_active or e.employee_id in all_presences or e. employee_id in all_correction_times or e.employee_id in special_activities or e.employee_id in employee_with_events, dao.employee_dao.list_overview())) self.employees = employees chrono.chrono_click() day_max = calendar.monthrange(self.base_date.year, self.base_date.month)[1] t_start = datetime(self.base_date.year, self.base_date.month, 1) t_end = datetime(self.base_date.year, self.base_date.month, day_max, 23, 59, 59, 999999) self._table_model.setRowCount(len(employees)) self._table_model.setColumnCount(1 + day_max + 3) headers = QStandardItemModel(1, 1 + day_max + 3) headers.setHeaderData(0, Qt.Orientation.Horizontal, _("Employee")) for i in range(day_max): headers.setHeaderData(i + 1, Qt.Orientation.Horizontal, "{}".format(i + 1)) headers.setHeaderData(day_max + 1, Qt.Orientation.Horizontal, _("Correction")) headers.setHeaderData(day_max + 2, Qt.Orientation.Horizontal, _("Total")) headers.setHeaderData(day_max + 3, Qt.Orientation.Horizontal, _("Days off")) self.headers_view.setModel( headers) # qt's doc : The view does *not* take ownership self.header_model = headers self.headers_view.setModel( self.header_model) # qt's doc : The view does *not* take ownership # Compute all mondays indices monday = 0 if t_start.weekday() > 0: monday = 7 - t_start.weekday() all_mondays = [] while monday < day_max: all_mondays.append(monday) monday += 7 today = date.today() # mainlog.debug("Running on employees") for row in range(self._table_model.rowCount()): # Clear the line for col in range(0, 32): ndx = self._table_model.index(row, col) self._table_model.setData(ndx, None, Qt.BackgroundRole) self._table_model.setData(ndx, QBrush(Qt.GlobalColor.black), Qt.TextColorRole) self._table_model.setData(ndx, None, Qt.DisplayRole) self._table_model.setData(ndx, None, Qt.UserRole) self._table_model.setData(ndx, None, Qt.UserRole + 1) # else: # self._table_model.setData(ndx,None,Qt.BackgroundRole) # else: # self._table_model.setData(ndx,None,Qt.DisplayRole) # self._table_model.setData(ndx,None,Qt.BackgroundRole) # Mark mondays for col in all_mondays: # col + 1 to account for the employee column self._table_model.setData( self._table_model.index(row, col + 1), QBrush(QColor(230, 230, 255)), Qt.BackgroundRole) # Mark today if today.month == self.base_date.month and today.year == self.base_date.year: self._table_model.setData( self._table_model.index(row, today.day), QBrush(QColor(255, 255, 128)), Qt.BackgroundRole) row = 0 for employee in employees: # employees are sorted self._table_model.setData(self._table_model.index(row, 0), employee.fullname, Qt.DisplayRole) # FIXME Use a delegate self._table_model.setData(self._table_model.index(row, 0), employee.employee_id, Qt.UserRole) # FIXME Use a delegate correction = 0 if employee.employee_id in all_correction_times: correction = all_correction_times[employee.employee_id] self._table_model.setData( self._table_model.index(row, day_max + 1), duration_to_hm(correction, short_unit=True), Qt.DisplayRole) else: self._table_model.setData( self._table_model.index(row, day_max + 1), None, Qt.DisplayRole) presence = 0 if employee.employee_id in all_presences and len( all_presences[employee.employee_id]) > 0: import functools presence = functools.reduce( lambda acc, s: acc + s, all_presences[employee.employee_id], 0) presence += correction if presence != 0: self._table_model.setData(ndx, QBrush(Qt.GlobalColor.black), Qt.TextColorRole) self._table_model.setData( self._table_model.index(row, day_max + 2), duration_to_hm(presence, short_unit=True), Qt.DisplayRole) else: self._table_model.setData( self._table_model.index(row, day_max + 2), None, Qt.DisplayRole) if employee.employee_id in all_presences and len( all_presences[employee.employee_id]) > 0: for b in range(len(all_presences[employee.employee_id])): ndx = self._table_model.index(row, b + 1) p = all_presences[employee.employee_id][b] if p > 0: self._table_model.setData( ndx, duration_to_hm(p, short_unit=True), Qt.DisplayRole) self._table_model.setData(ndx, p, Qt.UserRole) if p >= 4 and p <= 8: # Regular work load self._table_model.setData( ndx, QBrush(QColor(192, 255, 192)), Qt.BackgroundRole) elif p > 8 or (p < 4 and p > 0): # Problematic work load self._table_model.setData( ndx, QBrush(QColor(255, 192, 192)), Qt.BackgroundRole) if employee.employee_id in special_activities: sa_of_employee = special_activities[employee.employee_id] for sa in sa_of_employee: start = max(t_start, sa.start_time) end = min(t_end, sa.end_time) for i in range(start.day, end.day + 1): ndx = self._table_model.index(row, i) self._table_model.setData(ndx, QBrush(QColor(255, 128, 0)), Qt.BackgroundRole) # self._table_model.setData(self._table_model.index(row,b+1),Qt.AlignRight | Qt.AlignVCenter,Qt.TextAlignmentRole) row += 1 # Display day events employee_id_to_row = dict() # little accelerator for row in range(len(employees)): employee_id_to_row[employees[row].employee_id] = row # Compute days off totals and show them self.day_event_totals = dict([(e.employee_id, dict()) for e in employees]) for day_event in all_events_in_month: # mainlog.debug("employee_id = {}".format(day_event.employee_id)) # if day_event.employee_id not in self.day_event_totals: # mainlog.debug(self.day_event_totals) t = self.day_event_totals[day_event.employee_id] if day_event.event_type not in t: t[day_event.event_type] = day_event.duration else: t[day_event.event_type] += day_event.duration for employee in employees: # employees are sorted t = self.day_event_totals[employee.employee_id] mainlog.debug(t) total_off = sum(t.values()) mainlog.debug(total_off) row = employee_id_to_row[employee.employee_id] mainlog.debug(row) if total_off: self._table_model.setData( self._table_model.index(row, day_max + 3), nice_round(total_off), Qt.DisplayRole) else: self._table_model.setData( self._table_model.index(row, day_max + 3), None, Qt.DisplayRole) # Show days off for day_event in all_events_in_month: row = employee_id_to_row[day_event.employee_id] col = day_event.date.day fg = bg = None if day_event.event_type in self.DAY_EVENT_PALETTE: fg, bg = self.DAY_EVENT_PALETTE[day_event.event_type] else: fg, bg = Qt.GlobalColor.red, Qt.GlobalColor.gray ndx = self._table_model.index(row, col) self._table_model.setData(ndx, day_event.day_event_id, Qt.UserRole + 1) # The problem here is to nicely blend the fact # that you can have a day event mixed with actual work # the very same day. Here's a poor man solution. active_time = self._table_model.data(ndx, Qt.UserRole) if not active_time: self._table_model.setData( ndx, DayEventType.short_code(day_event.event_type), Qt.DisplayRole) self._table_model.setData(ndx, QBrush(fg), Qt.TextColorRole) self._table_model.setData(ndx, QBrush(bg), Qt.BackgroundRole) else: self._table_model.setData(ndx, QBrush(fg), Qt.TextColorRole) self._table_model.setData(ndx, QBrush(bg), Qt.BackgroundRole) self._table_model.setData( ndx, duration_to_hm(active_time, short_unit=True) + DayEventType.short_code(day_event.event_type), Qt.DisplayRole) chrono.chrono_click() #for i in range(len(all_mondays)): self.table_view.resizeColumnsToContents() # mainlog.debug("Reset selection") ndx = self.table_view.currentIndex() self.table_view.selectionModel().clear() # self.table_view.selectionModel().clearSelection() # self.table_view.selectionModel().select( self.table_view.model().index(ndx.row(),ndx.column()), QItemSelectionModel.Select) # self.table_view.selectionModel().select( self.table_view.model().index(ndx.row(),ndx.column()), QItemSelectionModel.Select) self.table_view.selectionModel().setCurrentIndex( self.table_view.model().index(ndx.row(), ndx.column()), QItemSelectionModel.Select) self.cell_entered(self.table_view.currentIndex()) @Slot() def edit_tars(self): employee_id = self._employee_id_on_row(self.table_view.currentIndex()) d = date( self.base_date.year, self.base_date.month, min( calendar.monthrange(self.base_date.year, self.base_date.month)[1], max(1, ndx.column()))) dialog = TimeReportingScannerDialog(self) dialog.set_data(datetime(d.year, d.month, d.day, 6, 0), employee_id) dialog.exec_() if dialog.result() == QDialog.Accepted: # pub.sendMessage('time_report.changed') self.timetrack_changed.emit() self.refresh_action() @Slot() def show_actions(self): button = self.action_menu.parent() p = button.mapToGlobal(QPoint(0, button.height())) self.action_menu.exec_(p) @Slot() def delete_holidays(self): ndx = self.table_view.currentIndex() employee_id = self._employee_id_on_row(ndx) d = date(self.base_date.year, self.base_date.month, ndx.column()) if dao.special_activity_dao.delete_by_employee_and_date( employee_id, d): self.refresh_panel() @Slot() def create_holidays(self): employee_id = self._employee_id_on_row(self.table_view.currentIndex()) left_col = 1000 right_col = 0 for ndx in self.table_view.selectionModel().selectedIndexes(): c = ndx.column() left_col = min(c, left_col) right_col = max(c, right_col) d_start = date( self.base_date.year, self.base_date.month, min( calendar.monthrange(self.base_date.year, self.base_date.month)[1], max(1, left_col))) d_end = date( self.base_date.year, self.base_date.month, min( calendar.monthrange(self.base_date.year, self.base_date.month)[1], max(1, right_col))) dialog = HolidaysDialog(self) sa = SpecialActivity() sa.employee_id = employee_id sa.reporter_id = user_session.user_id sa.encoding_date = date.today() sa.start_time = datetime(d_start.year, d_start.month, d_start.day, 6, 0) sa.end_time = datetime(d_end.year, d_end.month, d_end.day, 14, 0) dialog.setup(sa, dao.employee_dao.find_by_id(employee_id).fullname) # dialog.set_data(employee,self.base_date,c) dialog.exec_() if dialog.result() == QDialog.Accepted: dao.special_activity_dao.save(sa) self.refresh_action() @Slot() def edit_month_correction(self): employee_id = self._employee_id_on_row(self.table_view.currentIndex()) if employee_id: employee = dao.employee_dao.find_by_id(employee_id) c = dao.month_time_synthesis_dao.load_correction_time( employee_id, self.base_date.year, self.base_date.month) dialog = MonthTimeCorrectionDialog(self) dialog.set_data(employee.fullname, self.base_date, c) dialog.exec_() if dialog.result() == QDialog.Accepted: c = dao.month_time_synthesis_dao.save(employee_id, self.base_date.year, self.base_date.month, dialog.correction_time) self.refresh_action() @Slot() def month_today(self): self.base_date = date.today() self.refresh_action() @Slot() def month_before(self): m = self.base_date.month if m > 1: self.base_date = date(self.base_date.year, m - 1, 1) else: self.base_date = date(self.base_date.year - 1, 12, 1) self.refresh_action() @Slot() def month_after(self): m = self.base_date.month if self.base_date.year < date.today().year + 1 \ or m < date.today().month: if m < 12: self.base_date = date(self.base_date.year, m + 1, 1) else: self.base_date = date(self.base_date.year + 1, 1, 1) self.refresh_action() @Slot() def edit_timetrack_no_ndx(self): ndx = self.table_view.currentIndex() if ndx.isValid() and ndx.column() >= 0 and ndx.row() >= 0: self.edit_timetrack(ndx) else: showWarningBox(_("Can't edit"), _("You must first select a day/person.")) timetrack_changed = Signal() @Slot(QModelIndex) def edit_timetrack(self, ndx): global dao global user_session if ndx.column() >= 1: edit_date = date( self.base_date.year, self.base_date.month, ndx.column()) # +1 already in because of employee's names employee_id = self._employee_id_on_row(ndx) tars = dao.task_action_report_dao.get_reports_for_employee_id_on_date( employee_id, edit_date) if len(tars) == 0: d = EditTimeTracksDialog(self, dao, edit_date) d.set_employee_and_date(employee_id, edit_date) d.exec_() if d.result() == QDialog.Accepted: self.refresh_action() self.timetrack_changed.emit() d.deleteLater() else: edit_date = datetime(self.base_date.year, self.base_date.month, ndx.column(), hour=6) from koi.TimeReportingScanner import TimeReportingScannerDialog d = TimeReportingScannerDialog(self) d.set_data(edit_date, employee_id) # or 16 d.exec_() if d.result() == QDialog.Accepted: self.refresh_action() self.timetrack_changed.emit() d.deleteLater() @Slot() def editTaskActionReports(self): if not user_session.has_any_roles(['TimeTrackModify']): return m = self.base_date.month ndx = self.table_view.currentIndex() if ndx.isValid() and ndx.column() >= 0 and ndx.row() >= 0: edit_date = date( self.base_date.year, m, ndx.column()) # +1 already in because of employee's names employee = self._table_model.data( self._table_model.index(ndx.row(), 0), Qt.UserRole) # FIXME Use a delegate d = EditTaskActionReportsDialog(dao, self, edit_date) d.set_employee_date(employee, edit_date) d.exec_() if d.result() == QDialog.Accepted: self.refresh_action() d.deleteLater() else: showWarningBox(_("Can't edit"), _("You must first select a day/person.")) # @Slot(QModelIndex) # def timetrack_changed(self,ndx): # selected_timetrack = self.controller.model.object_at(ndx) # # Update the colors in the timetrack views # # to show what action reports correspond to the # # selected timetrack # self.controller_actions.model.current_timetrack = selected_timetrack # self.controller_actions.model.beginResetModel() # self.controller_actions.model.endResetModel() # # Make sure the first of the action reports is shown in the # # table # action_reports = self.controller_actions.model.objects # for i in range(len(action_reports)-1,-1,-1): # if action_reports[i] and action_reports[i].timetrack == selected_timetrack: # self.controller_actions.view.scrollTo(self.controller_actions.model.index(i,0)) # break def _make_table_header(self): pass def __init__(self, parent, find_order_action_slot): super(PresenceOverviewWidget, self).__init__(parent) self.set_panel_title(_("Presence overview")) self.base_date = date.today() headers = QStandardItemModel(1, 31 + 3) self._table_model = QStandardItemModel(1, 31 + 3, None) self.headers_view = QHeaderView(Qt.Orientation.Horizontal, self) self.header_model = headers self.headers_view.setResizeMode(QHeaderView.ResizeToContents) self.headers_view.setModel( self.header_model) # qt's doc : The view does *not* take ownership self.table_view = TableViewSignaledEvents(None) self.table_view.setModel(self._table_model) self.table_view.setHorizontalHeader(self.headers_view) self.table_view.verticalHeader().hide() self.table_view.setAlternatingRowColors(True) self.table_view.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_view.setContextMenuPolicy(Qt.CustomContextMenu) self.table_view.customContextMenuRequested.connect( self.popup_context_menu) self.copy_action = QAction(_("Copy order parts"), self.table_view) self.copy_action.triggered.connect(self.copy_slot) self.copy_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_C)) self.copy_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.table_view.addAction(self.copy_action) self.select_all_action = QAction(_("Select all"), self.table_view) self.select_all_action.triggered.connect(self.select_all_slot) self.select_all_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_A)) self.select_all_action.setShortcutContext( Qt.WidgetWithChildrenShortcut) self.table_view.addAction(self.select_all_action) # self.table_view.setSelectionBehavior(QAbstractItemView.SelectItems) # self.table_view.setSelectionMode(QAbstractItemView.SingleSelection) navbar = NavBar(self, [(_("Month before"), self.month_before), (_("Today"), self.month_today), (_("Action"), self.show_actions), (_("Month after"), self.month_after), (_("Find"), find_order_action_slot)]) self.action_menu = QMenu(navbar.buttons[2]) navbar.buttons[2].setObjectName("specialMenuButton") navbar.buttons[4].setObjectName("specialMenuButton") self._make_days_off_menu_and_action_group() list_actions = [ # (_("Edit"),self.edit_tars, None, None), (_("Edit"), self.edit_timetrack_no_ndx, None, None), (_("Month correction"), self.edit_month_correction, None, [RoleType.modify_monthly_time_track_correction]), (self.days_off_menu, None), (self.copy_action, None), (self.select_all_action, None) ] # (_("Insert holidays"),self.create_holidays, None, None), # (_("Delete holidays"),self.delete_holidays, None, None) ] populate_menu(self.action_menu, self, list_actions) # mainlog.debug("tile widget") self.title_box = TitleWidget(_("Presence Overview"), self, navbar) self.vlayout = QVBoxLayout(self) self.vlayout.setObjectName("Vlayout") self.vlayout.addWidget(self.title_box) self.hours_per_pers_subframe = SubFrame(_("Overview"), self.table_view, self) self.vlayout.addWidget(self.hours_per_pers_subframe) self.time_report_view = TimeReportView(self) self.days_off_panel = self._make_total_days_off_panel() vbox = QVBoxLayout() vbox.addWidget(self.days_off_panel) vbox.addStretch() vbox.setStretch(0, 0) vbox.setStretch(1, 1) hlayout = QHBoxLayout() hlayout.addWidget(self.time_report_view) hlayout.addLayout(vbox) hlayout.setStretch(0, 1) self.detail_subframe = SubFrame(_("Day"), hlayout, self) self.vlayout.addWidget(self.detail_subframe) self.setLayout(self.vlayout) # dbox = QVBoxLayout() # dbox.addWidget(QLabel("kjkljkj")) # self.total_active_hours = LabeledValue(_("Total activity")) # dbox.addWidget(self.total_active_hours) # hbox = QHBoxLayout() # hbox.addWidget(self.table_view) # hbox.addLayout(dbox) # self.selection_model = self.table_view.selectionModel() # mainlog.debug(m) #sm = QItemSelectionModel(self.table_view.model()) #sm.setModel(self.table_view.model()) # self.table_view.setSelectionModel(self.selection_model) self.table_view.selectionModel().currentChanged.connect( self.cell_entered) self.table_view.doubleClickedCell.connect(self.edit_timetrack) def _selection_to_period(self): left_col = 1000 right_col = 0 for ndx in self.table_view.selectionModel().selectedIndexes(): c = ndx.column() left_col = min(c, left_col) right_col = max(c, right_col) d_start = date( self.base_date.year, self.base_date.month, min( calendar.monthrange(self.base_date.year, self.base_date.month)[1], max(1, left_col))) d_end = date( self.base_date.year, self.base_date.month, min( calendar.monthrange(self.base_date.year, self.base_date.month)[1], max(1, right_col))) return d_start, d_end def _toggle_days_off_actions(self): day_max = calendar.monthrange(self.base_date.year, self.base_date.month)[1] ndx = self.table_view.currentIndex() can_add = can_remove = False if ndx.column() >= 1 and ndx.column() <= day_max: day_event_id = ndx.data(Qt.UserRole + 1) can_add = True # if not day_event_id: # day = ndx.column() - 1 # # employee_id = self._employee_id_on_row(ndx) # if employee_id in self.all_presences: # if not self.all_presences[employee_id][day]: # can_add = True # else: # can_add = True # else: # can_add = True can_remove = day_event_id is not None self.days_off_add_submenu.setEnabled(can_add) for actions in self.days_off_action_group.actions(): actions.setEnabled(can_add) self.day_off_remove_action.setEnabled(can_remove) def _add_day_off(self, action): if action.data() != 'Remove': day_event_type, day_event_duration = action.data() day_event_type = DayEventType.from_str(day_event_type) mainlog.debug("selected action {} {}".format( day_event_type, day_event_duration)) ndx = self.table_view.currentIndex() day_event_id = ndx.data(Qt.UserRole + 1) # if day_event_id: # showWarningBox(_("There's already a day off here")) # return employee_id = self._employee_id_on_row(ndx) if employee_id in self.all_presences: day = ndx.column() - 1 mainlog.debug("_add_day_off : employee_id={}, day={}".format( employee_id, day)) mainlog.debug(self.all_presences[employee_id]) mainlog.debug(type(self.all_presences[employee_id])) # if self.all_presences[employee_id][day]: # showWarningBox(_("One can't add day off where there is activity")) # return else: mainlog.debug( "_add_day_off : employee_id={} not yet known".format( employee_id)) day_event = DayEvent() day_event.employee_id = employee_id day_event.event_type = day_event_type day, last_day = self._selection_to_period() if day_event_duration in (0.5, 1): days_durations = [(day, day_event_duration)] else: days_durations = [] day, last_day = self._selection_to_period() while day <= last_day: days_durations.append( (day, 1)) # One full work day on the day day += timedelta(1) # mainlog.debug(days_durations) mainlog.debug("Creating day event of type {}".format( day_event.event_type)) try: people_admin_service.set_event_on_days(day_event, days_durations) except ServerException as ex: showErrorBox(ex.translated_message) self.refresh_action() def _remove_day_off(self): # Grab all the selected events event_ids = [] for ndx in self.table_view.selectionModel().selectedIndexes(): day_event_id = ndx.data(Qt.UserRole + 1) if day_event_id: mainlog.debug("Removing event: {}".format(day_event_id)) event_ids.append(day_event_id) # Remove them if event_ids: people_admin_service.remove_events(event_ids) self.refresh_action() def _make_days_off_menu_and_action_group(self): # We use a action group to be able to use the data() of actions # when action is tigerred # Call this ONLY ONCE because there are signal/slot connections. self.days_off_menu = QMenu(_("Day off")) self.days_off_add_submenu = QMenu(_("Set day off")) self.days_off_action_group = QActionGroup(self) for det in DayEventType.symbols(): a_one = QAction(_("Set one day"), self.days_off_action_group) a_one.setData((det.value, 1)) a_half = QAction(_("Set half day"), self.days_off_action_group) a_half.setData((det.value, 0.5)) a_period = QAction(_("Set period"), self.days_off_action_group) a_period.setData((det.value, 2)) self.days_off_action_group.addAction(a_one) self.days_off_action_group.addAction(a_half) self.days_off_action_group.addAction(a_period) m = QMenu(_("Set time off for {}").format(det.description)) m.addAction(a_one) m.addAction(a_half) m.addAction(a_period) self.days_off_add_submenu.addMenu(m) self.days_off_action_group.triggered.connect(self._add_day_off) self.day_off_remove_action = QAction(_("Remove day off"), self) self.day_off_remove_action.triggered.connect(self._remove_day_off) # Now we have the actions, we build the menu self.days_off_menu.addMenu(self.days_off_add_submenu) self.days_off_menu.addAction(self.day_off_remove_action) @Slot(QPoint) def popup_context_menu(self, position): self.days_off_menu.exec_(QCursor.pos()) @Slot() def select_all_slot(self): m = self.table_view.model() all = QItemSelection(m.index(0, 0), m.index(m.rowCount() - 1, m.columnCount() - 1)) self.table_view.selectionModel().select(all, QItemSelectionModel.Select) @Slot() def copy_slot(self): # Collect the rows indices indices = self.table_view.selectedIndexes() if not indices: return min_row = max_row = indices[0].row() min_col = max_col = indices[0].column() def min_max(minimum, v, maximum): if v < minimum: return v, maximum elif v > maximum: return minimum, v else: return minimum, maximum for ndx in self.table_view.selectedIndexes(): min_row, max_row = min_max(min_row, ndx.row(), max_row) min_col, max_col = min_max(min_col, ndx.column(), max_col) mainlog.debug("Copy from {},{} to {},{}".format( min_row, min_col, max_row, max_col)) day_max = calendar.monthrange(self.base_date.year, self.base_date.month)[1] s = "" for r in range(min_row, max_row + 1): d = [] for c in range(min_col, max_col + 1): ndx = self._table_model.item(r, c) if c == 0 or c > day_max: d.append(ndx.data(Qt.DisplayRole) or "") else: t = ndx.data(Qt.UserRole + 1) # Day off if t: t = ndx.data(Qt.DisplayRole) else: hours = ndx.data(Qt.UserRole) # Activity hours if hours is not None: t = str(hours).replace('.', ',') else: t = "" d.append(t) s += "\t".join(d) + u"\n" QApplication.clipboard().setText(s)
class OperationsOverviewWidget(QWidget): def __init__(self,parent=None): super(OperationsOverviewWidget,self).__init__(parent) self.operation_prototype = [] self.operation_prototype.append( OperationDefinitionPrototype('operation_definition_id',_('Op.'),operation_definition_cache.all_on_order_part(), editable=False)) self.operation_prototype.append( TextAreaPrototype('description',_('Description'),nullable=True,editable=False)) self.operation_prototype.append( FloatNumberPrototype('value',_('Value'),nullable=True,editable=False)) self.operation_prototype.append( DurationPrototype('planned_hours',_('Planned time'),nullable=True,editable=False)) # operation_prototype.append( DurationPrototype('t_reel',_('Used time'),nullable=False,editable=False)) self.operation_prototype.append( DurationPrototype('done_hours',_('Imputations'),editable=False)) # operation_prototype.append( TextLinePrototype('note',_('Note'),editable=True,nullable=True,hidden=True)) self.model = PrototypedModelView(self.operation_prototype,self) self.view = PrototypedQuickView(self.operation_prototype,self) self.view.setModel(self.model) self.view.verticalHeader().hide() self.view.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents) # Description column wide enough self.view.horizontalHeader().setResizeMode(1,QHeaderView.Stretch) self.view.setWordWrap(True) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSelectionMode(QAbstractItemView.ExtendedSelection) # self.controller_operation = PrototypeController(self, # self.operation_prototype, # ProxyTableView(None,self.operation_prototype)) # self.controller_operation.view.verticalHeader().hide() # # self.controller_operation.setModel(TrackingProxyModel(None,operation_prototype)) # self.controller_operation.setModel(TrackingProxyModel(self,self.operation_prototype)) # self.controller_operation.view.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents) # Description column wide enough # self.controller_operation.view.horizontalHeader().setResizeMode(1,QHeaderView.Stretch) # self.controller_operation.view.setSelectionBehavior(QAbstractItemView.SelectRows) layout = QHBoxLayout() layout.setContentsMargins(0,0,0,0) layout.addWidget(self.view) self.setLayout(layout) self.copy_operations_action = QAction(_("Copy operations"),self.view) self.copy_operations_action.triggered.connect( self.copy_operations_slot) self.copy_operations_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_C)) self.copy_operations_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.view.addAction(self.copy_operations_action) def fill_order_part(self, order_part_id): """ Fill with order part's data. Doesn't keep any reference to the order part. """ if order_part_id: mainlog.debug("fill_order_part : triggered !") operations = dao.operation_dao.find_by_order_part_id_frozen(order_part_id) mainlog.debug("fill_order_part : build model !") self.model.buildModelFromObjects(operations) mainlog.debug("fill_order_part : build model complete !") self.view.verticalHeader().setResizeMode(QHeaderView.ResizeToContents) else: self.model.clear() ndx = self.model.index(0,0) mainlog.debug("fill_order_part : setCurrentIndex !") self.view.setCurrentIndex(ndx) mainlog.debug("fill_order_part : setCurrentIndex done!") @Slot() def copy_operations_slot(self): mainlog.debug("copy_operations_slot") view = self.view model = self.model # Collect the rows indices rows = set() for ndx in view.selectedIndexes(): if ndx.row() >= 0: rows.add(ndx.row()) # There are no guarantee on the selectedIndexes order rows = sorted(list(rows)) # Copy for elsewhere in Horse if len(rows): operations = [] for row_ndx in rows: operation = model.object_at(row_ndx) mainlog.debug(operation) operations.append(operation) copy_paste_manager.copy_operations(operations) else: # If nothing to copy then we leave the copy/paste clipboard # as it is. So one could paste again what he copied before. pass