Esempio n. 1
0
    def add_cat_btn_pressed(self):
        '''
        Add a new category.
        Preventitive validation means that if we made it here we must have a category
        name of valid length. Just need to check not reserved and not already existing
        '''
        category_name = self.manageRemCatsLineEdit.text().strip()

        # Check new category name is not a reserved word
        if category_name.lower() in ['upcoming', 'complete', 'uncategorized', 'categories', 'category']:
            QtGui.QMessageBox.warning(self, "Reserved warning", unicode("Choose a different name"))
            return

        # Immediately add new category to database and fire signal to reflect
        # in main tree. If exists already handle integrity error.
        try:
            with session_scope() as session:
                c = Category(category_name=category_name)
                session.add(c)
            item = QtGui.QListWidgetItem(category_name, self.manageRemCatsListWidget)
            item.setSelected(True)
            self.manageRemCatsListWidget.sortItems()
            self.categories_changed.emit()
        except exc.IntegrityError as int_exc:
            # The with session_scope() should have handled the rollback
            logger.debug(int_exc)
            QtGui.QMessageBox.warning(self, "Already exists warning", unicode('This category already exists'))
Esempio n. 2
0
    def query_db(self):
        '''
        This is ran every <interval> milliseoncds. We refresh the humanized
        dates in the reminder due column and we check if any reminders are due

        Note that sqlalchemy session in this thread is not the same session
        as in the main thread. The scoped_session ensures in one thread
        we can keep a kind of global session. It saves passing around copies
        of the same session to different places, but it does not ensure same
        session between threads. We need to be careful that changes to db
        in this thread don't break other thread changes etc. Using sessions
        just for the duration of work and not eternal sessions that get
        init  during __init__ of main window and worker thread is also essential
        This ensures the sessions are fresh and not out of date with db after
        changes by the other thread...This is also consider good practice
        see http://docs.sqlalchemy.org/en/latest/orm/session_basics.html#when-do-i-construct-a-session-when-do-i-commit-it-and-when-do-i-close-it

        In this app, we can simply  restrict this worker to only reading the db
        and never writing to it. We can set the complete=True after the signal in the main thread
        '''
        # Use this loop to also refresh the human dates (e.g. "In 1 hour" in table)
        self.refresh_human_dates.emit()
        reminders = []
        with session_scope() as session:
            reminders = session.query(Reminder).filter(
                Reminder.complete == False).filter(
                    func.DATETIME(Reminder.due, 'utc') <= func.DATETIME(
                        'now', 'utc')).all()
            logger.debug('Got %i reminders due...' % len(reminders))
            for reminder in reminders:
                self.reminderisdue.emit(str(reminder.reminder_id))
Esempio n. 3
0
    def init_list(self):
        '''
        Build list widget according to db
        '''
        # Init
        all_categories = []
        selected_categories = []

        try:
            with session_scope() as session:
                all_categories = session.query(Category.category_name).order_by(Category.category_name).all()
                all_categories = [c[0] for c in all_categories]
                if self.edit_reminder_id is not None:
                    reminder = session.query(Reminder).get(int(self.edit_reminder_id))
                    selected_categories = [category.category_name for category in reminder.categories]
        except Exception as cat_exc:
            QtGui.QMessageBox.warning(self, "Unexpected error", unicode('Could not init categories list'))
            logger.error(str(cat_exc))
        else:
            logger.debug('All categories is %s' % all_categories)
            logger.debug('Selected categories is %s' % selected_categories)
            for category in all_categories:
                # Add to list widget
                item = QtGui.QListWidgetItem(category, self.manageRemCatsListWidget)
                if category in selected_categories:
                    item.setSelected(True)

        # Ensure ordered
        self.manageRemCatsListWidget.sortItems()
Esempio n. 4
0
    def remove_cat_action_triggered(self):
        '''
        Delete the category selected in the tree.

        The user must have selected a category and it must be one of
        the non-default categories (not [all, complete, uncategorized])
        '''
        logger.debug('Delete category')
        cat = self.mainTreeWidget.currentItem()
        root = self.mainTreeWidget.topLevelItem(0)
        if cat:
            if cat.parent() and root.indexOfChild(cat) > 2:
                category_name = cat.text(self.mainTreeWidget.currentColumn())
                with session_scope() as session:
                    session.query(Category).filter(
                        Category.category_name == category_name).delete()
                self.refresh_tree()
                self.refresh_table()
                QtGui.QMessageBox.information(
                    self, "Deleted category",
                    "Successfully deleted category %s" % category_name)
            else:
                QtGui.QMessageBox.warning(
                    self, "Select category",
                    "You must select a category other than default categories."
                )
        else:
            QtGui.QMessageBox.warning(self, "Select category",
                                      "You must select a category.")
Esempio n. 5
0
 def handle_time_zone_changed(self):
     '''
     So we can send our own data
     '''
     if self.tzListWidget.currentItem():
         self.time_zone = self.tzListWidget.currentItem().text()
         logger.debug('Change tz to %s' % self.time_zone)
         self.time_zone_changed.emit(self.time_zone)
Esempio n. 6
0
    def refresh_tree(self):
        '''
        Re-build the tree from database.

        Record the current category item, and if it is still present
        after the re-build set it as the current, else set 'Upcoming' category as current.

        Note that the root is the "Categories" item, the first 3 of its children
        are static, mandatory categories, "Upcoming", "Complete" and "Uncategorized"
        that don't come from the database - they are fixed. We only delete
        the custom user categories that come after these...
        '''
        # Record the current category
        old_category = self.mainTreeWidget.currentItem()
        if old_category is not None:
            old_category = old_category.text(
                self.mainTreeWidget.currentColumn())

        # Reload all the categories from the database
        categories = []
        try:
            with session_scope() as session:
                categories = session.query(Category.category_name).order_by(
                    Category.category_name).all()
                categories = [c[0] for c in categories]
        except Exception as uexc:
            logger.error(str(uexc))
            QtGui.QMessageBox.error(self, 'Unexpected error',
                                    'Could not select query categories.')
            return
        logger.debug('All categories %s' % categories)

        # Rebuild the tree widget with the reloaded categories
        root = self.mainTreeWidget.topLevelItem(0)
        root.setExpanded(True)
        # Clear custom categories, reverse important to not mess up interator
        # First 3 kids are static, don't delete.
        for i in reversed(range(root.childCount())):
            if i > 2:
                root.removeChild(root.child(i))

        # Set 'Upcoming' selected by default but set old current cat as current if
        # it still exists
        all_item = root.child(0)
        self.mainTreeWidget.setCurrentItem(all_item)
        for category in categories:
            cat_child = QtGui.QTreeWidgetItem()
            cat_child.setText(0, category)
            # Icon
            cat_icon = QtGui.QIcon()
            cat_icon.addPixmap(QtGui.QPixmap(":/icons/icons/play-button.png"),
                               QtGui.QIcon.Normal, QtGui.QIcon.Off)
            cat_child.setIcon(0, cat_icon)
            root.addChild(cat_child)
            if category == old_category:
                logger.debug('Set %s as current category item' % category)
                self.mainTreeWidget.setCurrentItem(cat_child)
Esempio n. 7
0
    def export_action_triggered(self):
        """Database export handler"""

        # Build JSON
        jdump = {
            'timestamp': arrow.utcnow().timestamp,
            'reminders': [],
            'categories': []
        }
        try:
            with session_scope() as session:
                for r in session.query(Reminder).all():
                    rdict = r.as_dict()
                    rdict['category_ids'] = [
                        c.category_id for c in r.categories
                    ]
                    jdump['reminders'].append(rdict)
                jdump['categories'] = [
                    c.as_dict() for c in session.query(Category).all()
                ]
        except Exception as xp_exc:
            logger.error(str(xp_exc))
            QtGui.QMessageBox(self, "Unexpected Exception",
                              "Could not export to file")
            return

        dbFile = QtGui.QFileDialog.getSaveFileName(
            parent=None,
            caption="Export database to a file",
            directory=".",
            filter="QTierna JSON (*.json)")
        if dbFile[0]:
            try:
                with io.open(dbFile[0], 'w', encoding='utf-8') as f:
                    logger.debug(jdump)
                    f.write(json.dumps(jdump, ensure_ascii=False))
                    msg = (
                        "Successfully exported %i reminders and %i categories to a file\r\n%s"
                        % (len(jdump['reminders']), len(jdump['categories']),
                           (QtCore.QDir.toNativeSeparators(dbFile[0]))))
                    QtGui.QMessageBox.information(self, __appname__, msg)
            except Exception as xportexc:
                QtGui.QMessageBox.critical(
                    self, __appname__,
                    "Error exporting file, error is\r\n" + str(xportexc))
                return
Esempio n. 8
0
    def _init_widgets(self):
        '''
        Init the widgets according to self.reminder_dict
        '''
        # Defaults
        due = dt2str(get_utc_now())
        note = None
        complete = False
        if self.edit_reminder_id is not None:
            # For edit, load from existing, set title and enable 'Complete' chckbx
            self.setWindowTitle('Edit reminder')
            self.addEditRemDlgCompletecheckBox.setEnabled(True)
            with session_scope() as session:
                reminder = session.query(Reminder).get(
                    int(self.edit_reminder_id))
                due = reminder.due
                note = reminder.note
                complete = reminder.complete

        # Set date
        local_due = utcstr2local(due, self.time_zone)
        self.addEditRemDlgCalendarWidget.setSelectedDate(
            QtCore.QDate(local_due.year, local_due.month, local_due.day))
        # Set time
        self.addEditRemDlgTimeEdit.setTime(
            QtCore.QTime(local_due.time().hour,
                         local_due.time().minute))
        # Set note
        if note:
            self.addEditRemDlgTextEdit.document().setPlainText(unicode(note))
        # Set completed
        if complete:
            self.addEditRemDlgCompletecheckBox.setCheckState(QtCore.Qt.Checked)

        # If this edit is a Reschedule
        if self.reschedule is True:
            # This is in reaction to 'Reschedule' from notification dlg
            # Deselect 'complete' and also highlight time edit
            logger.debug(
                'Reschedule is True so overriding the complete check status')
            self.addEditRemDlgCompletecheckBox.setCheckState(
                QtCore.Qt.Unchecked)
            self.addEditRemDlgTimeEdit.selectAll()
            self.addEditRemDlgTimeEdit.setFocus()
Esempio n. 9
0
    def addedit_cat_action_triggered(self):
        '''
        Add or edit a category
        '''
        # Determine if add/edit
        action = self.sender()
        logger.debug('addedit cat action with sender %s' % action.objectName())
        if action is None or not isinstance(action, QtGui.QAction):
            return None

        if action.objectName() == 'actionEdit_Category':
            logger.debug('User wants to edit category')
            # Selected category
            cat = self.mainTreeWidget.currentItem()
            root = self.mainTreeWidget.topLevelItem(0)
            if cat:
                if cat.parent() and root.indexOfChild(cat) > 2:
                    category_name = cat.text(
                        self.mainTreeWidget.currentColumn())
                    category_id = None
                    with session_scope() as session:
                        category_id = session.query(
                            Category.category_id).filter(
                                Category.category_name ==
                                category_name).scalar()
                    if category_id is None:
                        QtGui.QMessageBox.warning(
                            self, "Does not exist",
                            "Category was not found with name %s" %
                            category_name)
                    dlg = AddEditCatDialog(edit_cat_id=category_id,
                                           parent=self)
                    dlg.categories_changed.connect(self.refresh_tree)
                    # The placeholder text doesn't show because focus is initially so do
                    dlg.setFocus()
                    if dlg.exec_():
                        logger.debug('Edited category %s' % category_name)
                else:
                    QtGui.QMessageBox.warning(
                        self, "Select category",
                        "You must select a category other than default categories."
                    )
            else:
                QtGui.QMessageBox.warning(self, "Select category",
                                          "You must select a category.")
        elif action.objectName() == 'actionAdd_Category':
            dlg = AddEditCatDialog(parent=self)
            dlg.categories_changed.connect(self.refresh_tree)
            # The placeholder text doesn't show because focus is initially
            # on the lineedit as the sole element. Could do something like
            dlg.setFocus()
            if dlg.exec_():
                logger.debug('Added new category...')
Esempio n. 10
0
def main():
    QtCore.QCoreApplication.setApplicationName("QTierna")
    QtCore.QCoreApplication.setApplicationVersion("0.1")
    QtCore.QCoreApplication.setOrganizationName("QTierna")
    QtCore.QCoreApplication.setOrganizationDomain("logicon.io")

    app = QtGui.QApplication(sys.argv)

    # Debug phonon issue
    for lppath in app.libraryPaths():
        logger.debug(lppath)

    if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
        QtGui.QMessageBox.critical(
            None, "Systray",
            "I couldn't detect any system tray on this system.")
        sys.exit(1)

    form = Main()
    form.show()
    app.exec_()
Esempio n. 11
0
 def accept(self):
     # Override accept so we can first validate
     if self.is_valid():
         category_name = self.addEditCatLineEdit.text().strip()
         try:
             with session_scope() as session:
                 if self.edit_cat_id is not None:
                     category = session.query(Category).get(
                         int(self.edit_cat_id))
                     category.category_name = category_name
                     logger.debug('Edited cat with id %s' %
                                  self.edit_cat_id)
                 else:
                     category = Category(category_name=category_name)
                     session.add(category)
                     logger.debug('Added cat with name %s' % category_name)
         except exc.IntegrityError as int_exc:
             logger.debug(int_exc)
             QMessageBox.warning(self, "Already exists warning",
                                 unicode('This category already exists'))
             self.addEditCatLineEdit.setFocus()
             self.selectAll()
             return
         except Exception as uexc:
             logger.error(str(uexc))
             QMessageBox.warning(self, "Unexpected Error",
                                 unicode('Could not edit category.'))
             return
         else:
             # All good, accept after triggering tree refresh with sig
             self.categories_changed.emit()
             QDialog.accept(self)
Esempio n. 12
0
    def addedit_rem_action_triggered(self, reminder_id=None):
        """
        Opens the add or edit reminder dialog. For edit would be same but with edit_reminder_id

        A non-zero reminder_id is for the 'Reschedule' button click of the notification dlg.
        For both add/edit reminder actions reminder_id will be None, but for
        edit we'll get the reminder_id from the row selected in table
        """
        action = self.sender()
        reschedule = False
        if reminder_id is not None:
            reschedule = True
        elif action is None or not isinstance(action, QtGui.QAction):
            return None

        if action and action.objectName() == 'actionEdit_Reminder':
            # Ensure only one row selected, get item for that row
            logger.debug('User wants to edit reminder')
            indices = self.mainTableWidget.selectionModel().selectedRows()
            selected_rows = [index.row() for index in indices]
            if len(selected_rows) == 1:
                selected_row = selected_rows[0]
                reminder_id = self.mainTableWidget.item(selected_row, 4).text()
                logger.debug('Reminder with id %s' % reminder_id)
            else:
                QtGui.QMessageBox.warning(self, 'Select rows',
                                          'You must select one row.')
                return

        dialog = AddEditRemDialog(self.time_zone,
                                  edit_reminder_id=reminder_id,
                                  reschedule=reschedule,
                                  parent=self)
        dialog.categories_changed.connect(self.refresh_tree)
        if dialog.exec_():
            # Focus on 'Upcoming' category so we can see new reminder has been added
            root = self.mainTreeWidget.topLevelItem(0)
            all_item = root.child(0)
            self.mainTreeWidget.setCurrentItem(all_item)
            self.refresh_table()
Esempio n. 13
0
 def accept(self, edit=False):
     '''
     Save the new/edited Reminder if valid
     '''
     if self.is_valid():
         try:
             category_names = []
             if self.categoriesDlg is not None:
                 # User edited categories we must update
                 category_names = self.categoriesDlg._get_selected_categories(
                 )
             logger.debug('Selected categories were %s' % category_names)
             with session_scope() as session:
                 category_instances = []
                 if category_names:
                     category_instances = session.query(Category).filter(
                         Category.category_name.in_(category_names)).all()
                 if self.edit_reminder_id:
                     reminder = session.query(Reminder).get(
                         int(self.edit_reminder_id))
                 else:
                     reminder = Reminder()
                 reminder.due = self._get_reminder_utc_datetime_str()
                 reminder.complete = self._()
                 reminder.note = self._get_reminder_note()
                 if category_instances:
                     reminder.categories = category_instances
                 session.add(reminder)
         except exc.IntegrityError as int_exc:
             # Rollback already handled by scoped_session ctx manager
             logger.error(int_exc)
             QtGui.QMessageBox.warning(
                 self, "Already exists warning",
                 unicode('This reminder already exists'))
             return
         else:
             # All good, accept
             QtGui.QDialog.accept(self)
Esempio n. 14
0
 def eventFilter(self, widget, event):
     if event.type() == QtCore.QEvent.KeyPress:
         if widget is self.mainTreeWidget:
             key = event.key()
             if key == QtCore.Qt.Key_Delete:
                 logger.debug('Delete cat key event')
                 self.actionDelete_Category.triggered.emit()
                 return True
             elif key == QtCore.Qt.Key_Return:
                 logger.debug('Edit cat key event')
                 self.actionEdit_Category.triggered.emit()
                 return True
         elif widget is self.mainTableWidget:
             key = event.key()
             if key == QtCore.Qt.Key_Delete:
                 logger.debug('Delete rem key event')
                 self.actionRemove_Reminder.triggered.emit()
                 return True
             elif key == QtCore.Qt.Key_Return:
                 logger.debug('Edit rem key event')
                 self.actionEdit_Reminder.triggered.emit()
                 return True
     return QtGui.QMainWindow.eventFilter(self, widget, event)
Esempio n. 15
0
    def delete_cats_btn_pressed(self):
        '''
        Delete selected categories. Preventitive validation (enable/disable delete button)
        means always some categories selected if made it here
        '''
        # Get all checked in listviewwidget
        # delete from db
        selected_items = self.manageRemCatsListWidget.selectedItems()
        selected_category_names = [item.text() for item in selected_items]
        logger.debug('Got %i categories for deletion..' % len(selected_category_names))
        try:
            with session_scope() as session:
                session.query(Category).filter(Category.category_name.in_(selected_category_names)).delete(synchronize_session='fetch')
        except Exception as del_exc:
            QtGui.QMessageBox.warning(self, "Unexpected error", unicode('Could not delete categories'))
            logger.error(str(del_exc))
        else:
            logger.debug('Deleted %s' % selected_category_names)
            self.categories_changed.emit()

            # Delete these items from the list widget
            for selected_item in selected_items:
                logger.debug('Removing item %s from list widget' % selected_item)
                self.manageRemCatsListWidget.takeItem(self.manageRemCatsListWidget.row(selected_item))
Esempio n. 16
0
 def set_minimize_behavior(self, state):
     logger.debug('The minimize state is %s' % state)
     self.minimizeToTray = state
     self.settings.setValue("minimizeToTray", bool2str(state))
Esempio n. 17
0
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)
        self.setupUi(self)

        # Create the database tables if they don't exist
        db_create_tables()

        # ################### Saved app settings ########################
        self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
                                         QtCore.QSettings.UserScope, "QTierna",
                                         "QTierna")
        self.minimizeToTray = str2bool(
            self.settings.value("minimizeToTray", True))  # Exit behaviour
        self.time_zone = pytz.timezone(
            self.settings.value("time_zone",
                                get_localzone().zone))
        logger.debug('Settings loaded from %s. Initialized time_zone: %s' %
                     (self.settings.fileName(), self.time_zone))

        # ############# Reminders table config ##########################
        self.soon_color = QtGui.QColor(255, 0, 0, 127)  # Reminder soon due
        self.mainTableWidget.setEditTriggers(
            QtGui.QAbstractItemView.NoEditTriggers)  # Don't let user edit
        # Column widths
        total_width = self.mainTableWidget.width()
        self.mainTableWidget.setColumnWidth(0, (total_width / 10.0) * 2)
        self.mainTableWidget.setColumnWidth(1, (total_width / 10.0) * 2)
        self.mainTableWidget.setColumnWidth(2, (total_width / 10.0) * 6)
        # Hidden UTC and reminder ID columns
        self.mainTableWidget.setColumnWidth(3, 0)
        self.mainTableWidget.setColumnHidden(3, True)
        self.mainTableWidget.setColumnWidth(4, 0)
        self.mainTableWidget.setColumnHidden(4, True)
        # Table signals
        self.mainTableWidget.itemDoubleClicked.connect(self.table_dbl_click)

        # ########### Categories tree config #############################
        # Font bold to oblig cats, not sure why my qt designerui didnt apply
        myFont = QtGui.QFont()
        myFont.setBold(True)
        self.mainTreeWidget.topLevelItem(0).setFont(0, myFont)
        self.mainTreeWidget.topLevelItem(0).child(0).setFont(0, myFont)
        self.mainTreeWidget.topLevelItem(0).child(1).setFont(0, myFont)
        self.mainTreeWidget.topLevelItem(0).child(2).setFont(0, myFont)
        # Tree signals
        self.mainTreeWidget.itemSelectionChanged.connect(self.refresh_table)

        # ##################### Actions ####################################
        self.actionAdd_Reminder.triggered.connect(
            self.addedit_rem_action_triggered)
        self.actionEdit_Reminder.triggered.connect(
            self.addedit_rem_action_triggered)
        self.actionRemove_Reminder.triggered.connect(
            self.remove_rem_action_triggered)
        self.actionAdd_Category.triggered.connect(
            self.addedit_cat_action_triggered)
        self.actionEdit_Category.triggered.connect(
            self.addedit_cat_action_triggered)
        self.actionDelete_Category.triggered.connect(
            self.remove_cat_action_triggered)
        self.actionExport_Data.triggered.connect(self.export_action_triggered)
        self.actionImport_Data.triggered.connect(self.import_action_triggered)
        self.actionPreferences.triggered.connect(
            self.preferences_action_triggered)
        self.actionAbout.triggered.connect(self.about_action_triggered)

        # ##################### Worker thread #############################
        # Worker to run the eternal loop to check for due reminders
        # I'm doing it with moveToThread in this manner, rather than
        # just making the Worker class inherit from QThread
        # as apparently this is best practice now:
        #  https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
        # Alternatively put this in the if __main__ section with minor alts
        self.workerThread = QtCore.QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.workerThread)
        self.workerThread.started.connect(self.worker.check_reminders_loop)
        self.worker.reminderisdue.connect(self.launch_notification)
        self.worker.refresh_human_dates.connect(
            self.refresh_human_dates)  # Keep the humanized due dates refreshed
        self.workerThread.start()

        # #################### SysTray icon, menu ###########################
        # Init QSystemTrayIcon
        self.tray_icon = QtGui.QSystemTrayIcon(self)
        tray_icon = QtGui.QIcon()
        tray_icon.addPixmap(
            QtGui.QPixmap(":/icons/icons/alarm-clock-white.png"),
            QtGui.QIcon.Normal, QtGui.QIcon.Off)
        # self.tray_icon.setIcon(QtGui.QIcon("icons/alarm-clock-white.png"))
        self.tray_icon.setIcon(tray_icon)
        show_action = QtGui.QAction("Show", self)
        quit_action = QtGui.QAction("Exit", self)
        hide_action = QtGui.QAction("Hide", self)
        show_action.triggered.connect(self.show)
        hide_action.triggered.connect(self.hide)
        quit_action.triggered.connect(QtGui.qApp.quit)
        tray_menu = QtGui.QMenu()
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

        # ################### CONTEXT MENUS ##################################
        # Popup context menu when right-click on tree widget for add/edit/rem cat
        self.mainTreeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.mainTreeWidget.customContextMenuRequested.connect(
            self.on_tree_context_menu)
        self.tree_popup_menu = QtGui.QMenu(self)
        self.tree_popup_menu.addAction(self.actionAdd_Category)
        self.tree_popup_menu.addAction(self.actionEdit_Category)
        self.tree_popup_menu.addSeparator()
        self.tree_popup_menu.addAction(self.actionDelete_Category)
        # Popup context menu when right-click on row on the table widget for
        # add/edit/remove reminder
        self.mainTableWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.mainTableWidget.customContextMenuRequested.connect(
            self.on_table_context_menu)
        self.table_popup_menu = QtGui.QMenu(self)
        self.table_popup_menu.addAction(self.actionAdd_Reminder)
        self.table_popup_menu.addAction(self.actionEdit_Reminder)
        self.table_popup_menu.addSeparator()
        self.table_popup_menu.addAction(self.actionRemove_Reminder)

        # ############# Install event filters ###############################
        self.mainTreeWidget.installEventFilter(self)
        self.mainTableWidget.installEventFilter(self)

        # ############ Initial load of the tree and table ###################
        self.refresh_tree()
        self.refresh_table()
Esempio n. 18
0
    def refresh_table(self):
        """
        Refreshes (or initially loads) the table according to db

        First we find which (if any) category is selected, then we filter
        our reminders based on that.

        With a list of reminders we repopulate the table, setting human
        readable dates in due column, which tooltip for exact date, categories
        in the second column, with tooltip of all categories, note in the third
        column with tooltip of full note, and fourth and fifth hidden columns
        that record the utc and id. Storing the UTC in a hidden col is a convenience
        that saves having to query the db again with the id, e.g. in
        refresh_human_dates()

        If the category is the "Complete" category set a dynamic property
        that will trigger the alt stylsheet row coloring.
        """
        # Init
        reverse_dates = False
        root = self.mainTreeWidget.topLevelItem(0)
        category_name = None
        indx = None

        # Get current category if there is one
        cat = self.mainTreeWidget.currentItem()
        if cat:
            category_name = cat.text(self.mainTreeWidget.currentColumn())
            indx = root.indexOfChild(cat)

        with session_scope() as session:
            # Get reminder instances from database for the given category
            if indx == 0 and category_name == 'Upcoming':
                # Selected Upcoming
                reminders = session.query(Reminder).filter(
                    Reminder.complete == False).all()
            elif indx == 1 and category_name == 'Complete':
                # all completed
                reverse_dates = True
                reminders = session.query(Reminder).filter(
                    Reminder.complete == True).all()
            elif indx == 2 and category_name == 'Uncategorized':
                reminders = session.query(Reminder).filter(
                    Reminder.complete == False).filter(
                        ~Reminder.categories.any()).all()
            elif indx > 2 and category_name:
                reminders = session.query(Reminder).filter(
                    Reminder.complete == False).filter(
                        Reminder.categories.any(
                            Category.category_name == category_name)).all()
            else:
                # Nothing selected
                reminders = session.query(Reminder).filter(
                    Reminder.complete == False).all()
            logger.debug('Refreshing table: cat %s, indx: %s. Reminders %s' %
                         (category_name, indx, reminders))

            # Populate the table with the reminders sorted by datetimes
            reminders = sorted(reminders,
                               key=lambda reminder: datetime.strptime(
                                   reminder.due, '%Y-%m-%d %H:%M'),
                               reverse=reverse_dates)
            self.mainTableWidget.setRowCount(
                0)  # Delete rows ready to repopulate
            for inx, reminder in enumerate(reminders):
                # Insert the row
                self.mainTableWidget.insertRow(inx)

                # Due col
                utc_datetime_str = reminder.due  # UTC
                local_datetime_str = dt2str(
                    utcstr2local(utc_datetime_str, self.time_zone))
                arrow_utc_dt = arrow.get(utc_datetime_str, 'YYYY-MM-DD HH:mm')
                human_due = arrow_utc_dt.humanize()  # Human readable dt
                self.mainTableWidget.setItem(inx, 0,
                                             QtGui.QTableWidgetItem(human_due))
                self.mainTableWidget.item(inx,
                                          0).setToolTip(local_datetime_str)
                hours_before = (
                    (arrow_utc_dt - arrow.utcnow()).total_seconds()) / 3600.0
                if hours_before <= 24 and hours_before > 0:
                    # Highlight
                    self.mainTableWidget.item(inx,
                                              0).setBackground(self.soon_color)

                # Categories col
                categories = ', '.join([
                    category.category_name for category in reminder.categories
                ])
                catItem = QtGui.QTableWidgetItem(categories)
                catTip = u'<font color="black">%s</font>' % '<br/>'.join(
                    [c.category_name for c in reminder.categories])
                catItem.setToolTip(catTip)
                self.mainTableWidget.setItem(inx, 1, catItem)

                # Note col
                noteItem = QtGui.QTableWidgetItem(smart_truncate(
                    reminder.note))
                noteTip = u"<div style='width: 300px;'>%s</div>" % smart_truncate(
                    reminder.note, length=1000)
                noteItem.setToolTip(noteTip)
                self.mainTableWidget.setItem(inx, 2, noteItem)

                # Hidden cols for UTC string and ID
                self.mainTableWidget.setItem(
                    inx, 3, QtGui.QTableWidgetItem(utc_datetime_str))
                self.mainTableWidget.setItem(
                    inx, 4,
                    QtGui.QTableWidgetItem(unicode(reminder.reminder_id)))

        # Set the row coloring accordingly
        if category_name == 'Complete':
            # Could just directly update stylesheet on table,
            # but using dynamic properties and [complete=true]
            # targeting in the style sheet is a little nicer
            self.mainTableWidget.setProperty('complete', True)
            self.mainTableWidget.style().unpolish(self.mainTableWidget)
            self.mainTableWidget.style().polish(self.mainTableWidget)
        else:
            self.mainTableWidget.setProperty('complete', False)
            self.mainTableWidget.style().unpolish(self.mainTableWidget)
            self.mainTableWidget.style().polish(self.mainTableWidget)
Esempio n. 19
0
    def launch_notification(self, reminder_id):
        due = note = None
        try:
            with session_scope() as session:
                reminder = session.query(Reminder).get(int(reminder_id))
                due = reminder.due
                note = reminder.note
                reminder.complete = True
        except Exception as uexc:
            logger.error(str(uexc))
            QtGui.QMessageBox(self, 'Unexpected exception',
                              'Could not mark due reminder as complete')
            return

        # Get local datetime for output to user and format note as html
        local_due = dt2str(utcstr2local(due,
                                        self.time_zone,
                                        date_format='%Y-%m-%d %H:%M'),
                           date_format='%d %b %I:%M%p')
        htmlcontent = '<p>%s</p>' % note

        # QApplication.instance().beep()
        # if QtGui.QSound.isAvailable():
        #     # Seems I would have to recompile with NAS support, but
        #     # what does that mean for python when pyside was pip installed??
        #     QtGui.QSound.play("media/alarm_beep.wav")
        media = Phonon.MediaObject()
        audio = Phonon.AudioOutput(Phonon.MusicCategory)
        Phonon.createPath(media, audio)
        # alarm_file = os.path.join(os.getcwd(), 'media/alarm_beep.wav')
        alarm_file = resource_path('alarm_beep.wav')
        logger.debug('Trying to open alarm file...%s' % alarm_file)
        f = QtCore.QFile(alarm_file)
        if f.exists():
            source = Phonon.MediaSource(alarm_file)
            if source.type() != -1:  # -1 stands for invalid file
                media.setCurrentSource(source)
                media.play()
        else:
            logger.debug('Alert media missing: %s' % alarm_file)

        # Systray notification
        self.tray_icon.showMessage(unicode('Reminder due at %s' % local_due),
                                   smart_truncate(note, length=100),
                                   QtGui.QSystemTrayIcon.Information, 5000)

        # Dialog notification
        self.show()
        dlg = NotificationDialog()
        dlg.notificationTextBrowser.setHtml(htmlcontent)
        dlg.dtLabel.setText(local_due)
        dlg.setWindowTitle(unicode('Due at %s' % local_due))
        # Change std buttons to "Reschedule" and "Mark Complete".
        # Resched will set complete=False and launch the edit reminder with
        # time selected. "Mark Complete" does nothing, since we already
        # marked complete to prevent further popups
        dlg.notificationButtonBox.button(
            QtGui.QDialogButtonBox.Ok).setText('Mark Complete')
        dlg.notificationButtonBox.button(
            QtGui.QDialogButtonBox.Cancel).setText('Reschedule')
        if dlg.exec_():
            logger.debug(
                'User wants to close dlg and keep the reminder as completed')
        else:
            # Launch edit reminder
            logger.debug('User wants to reschedule')
            self.addedit_rem_action_triggered(reminder_id=reminder_id)

        # Refresh table to account for this reminder completion
        self.refresh_table()