예제 #1
0
 def _apply_query(self):
     # On top, we want exact matches (the name starts with the query). Then, we want matches
     # that contain all the letters, sorted in order of names that have query letters as close
     # to each other as possible.
     q = sort_string(self._search_query)
     matches1, rest = extract(lambda n: n.startswith(q), self._original_names)
     matches2, rest = extract(lambda n: q in n, rest)
     matches3, rest = extract(lambda n: has_letters(n, q), rest)
     matches3.sort(key=lambda n: letters_distance(n, q))
     self._filtered_names = matches1 + matches2 + matches3
     self.selected_index = max(self.selected_index, 0)
     self.selected_index = min(self.selected_index, len(self._filtered_names)-1)
예제 #2
0
 def _apply_query(self):
     # On top, we want exact matches (the name starts with the query). Then, we want matches
     # that contain all the letters, sorted in order of names that have query letters as close
     # to each other as possible.
     q = sort_string(self._search_query)
     matches1, rest = extract(lambda n: n.startswith(q),
                              self._original_names)
     matches2, rest = extract(lambda n: q in n, rest)
     matches3, rest = extract(lambda n: has_letters(n, q), rest)
     matches3.sort(key=lambda n: letters_distance(n, q))
     self._filtered_names = matches1 + matches2 + matches3
     self.selected_index = max(self.selected_index, 0)
     self.selected_index = min(self.selected_index,
                               len(self._filtered_names) - 1)
예제 #3
0
 def toggle_entries_reconciled(self, entries):
     """Toggle the reconcile flag of `entries`.
     """
     if not entries:
         return
     all_reconciled = not entries or all(entry.reconciled for entry in entries)
     newvalue = not all_reconciled
     action = Action(tr('Change reconciliation'))
     action.change_splits([e.split for e in entries])
     min_date = min(entry.date for entry in entries)
     splits = [entry.split for entry in entries]
     spawns, splits = extract(lambda s: isinstance(s.transaction, Spawn), splits)
     for spawn in spawns:
         action.change_schedule(spawn.transaction.recurrence)
     self._undoer.record(action)
     if newvalue:
         for split in splits:
             split.reconciliation_date = split.transaction.date
         for spawn in spawns:
             #XXX update transaction selection
             materialized_split = self._reconcile_spawn_split(spawn, spawn.transaction.date)
             action.added_transactions.add(materialized_split.transaction)
     else:
         for split in splits:
             split.reconciliation_date = None
     self._cook(from_date=min_date)
     self.notify('transaction_changed')
예제 #4
0
 def get_spawns(self, end, transactions, consumedtxns):
     # transactions will affect the amounts of the budget spawns
     # consumedtxns is a set of txns already "consumed" by a budget. It is the budget's
     # responsability to add txns to this set as it "consumes" them
     spawns = Recurrence.get_spawns(self, end)
     # No spawn in the past
     spawns = [spawn for spawn in spawns if spawn.date > date.today()]
     account = self.account
     budget_amount = self.amount if account.is_debit_account() else -self.amount
     relevant_transactions = set(t for t in transactions if account in t.affected_accounts())
     relevant_transactions -= consumedtxns
     for spawn in spawns:
         affects_spawn = lambda t: spawn.recurrence_date <= t.date <= spawn.date
         wheat, shaft = extract(affects_spawn, relevant_transactions)
         relevant_transactions = shaft
         txns_amount = sum(t.amount_for_account(account, budget_amount.currency) for t in wheat)
         if abs(txns_amount) < abs(budget_amount):
             spawn_amount = budget_amount - txns_amount
             if spawn.amount_for_account(account, budget_amount.currency) != spawn_amount:
                 spawn.amount = abs(spawn_amount)
                 spawn.set_splits([Split(spawn, account, spawn_amount), Split(spawn, self.target, -spawn_amount)])
         else:
             spawn.set_splits([])
         consumedtxns |= set(wheat)
     self._previous_spawns = spawns
     return spawns
예제 #5
0
 def draw_pie(self, data, circle_bounds):
     if not data:
         return
     circle_size = min(circle_bounds.w, circle_bounds.h)
     radius = circle_size / 2
     center = circle_bounds.center()
     
     # draw pie
     total_amount = sum(amount for _, amount, _ in data)
     start_angle = 0
     legends = []
     for legend_text, amount, color_index in data:
         fraction = amount / total_amount
         angle = fraction * 360
         self.view.draw_pie(center, radius, start_angle, angle, color_index)
         legend_angle = start_angle + (angle / 2)
         legend = Legend(text=legend_text, color=color_index, angle=legend_angle)
         legends.append(legend)
         start_angle += angle
     
     # compute legend rects
     _, legend_height = self.view.text_size('', FontID.Legend)
     for legend in legends:
         legend.base_point = point_in_circle(center, radius, legend.angle)
         legend_width, _ = self.view.text_size(legend.text, FontID.Legend)
         legend.text_rect = rect_from_center(legend.base_point, (legend_width, legend_height))
         legend.compute_label_rect()
     
     # make sure they're inside circle_bounds
     for legend in legends:
         pull_rect_in(legend.label_rect, circle_bounds)
     
     left, right = extract(lambda l: l.base_point.x < center.x, legends)
     # If any legend intersect, we start by sending everyone to their horizontal circle bounds.
     # Then, on each side, if anyone intersect, we go in "Spread mode", spreading all legends
     # vertically in a regular manner.
     if legends_intersect(legends):
         for legend in left:
             legend.label_rect.left = circle_bounds.left
         for legend in right:
             legend.label_rect.right = circle_bounds.right
     for side in (left, right):
         if legends_intersect(side):
             spread_vertically(side, circle_bounds)
     
     # draw legends
     # draw lines before legends because we don't want them being drawn over other legends
     if len(legends) > 1:
         for legend in legends:
             if not legend.should_draw_line():
                 continue
             self.view.draw_line(legend.label_rect.center(), legend.base_point, legend.color)
     for legend in legends:
         self.view.draw_rect(legend.label_rect, legend.color, BrushID.Legend)
         legend.compute_text_rect()
         self.view.draw_text(legend.text, legend.text_rect, FontID.Legend)
예제 #6
0
    def change_transactions(self, transactions):
        """Record imminent changes to ``transactions``.

        If any of the transactions are a :class:`.Spawn`, also record a change to their related
        schedule.
        """
        spawns, normal = extract(lambda t: isinstance(t, Spawn), transactions)
        self.changed_transactions |= set((t, t.replicate()) for t in normal)
        for schedule in set(spawn.recurrence for spawn in spawns):
            self.change_schedule(schedule)
예제 #7
0
 def collect_results(collect_all=False):
     # collect results and wait until the queue is small enough to accomodate a new results.
     nonlocal async_results, matches, comparison_count
     limit = 0 if collect_all else RESULTS_QUEUE_LIMIT
     while len(async_results) > limit:
         ready, working = extract(lambda r: r.ready(), async_results)
         for result in ready:
             matches += result.get()
             async_results.remove(result)
             comparison_count += 1
     progress_msg = tr("Performed %d/%d chunk matches") % (comparison_count, len(comparisons_to_do))
     j.set_progress(comparison_count, progress_msg)
예제 #8
0
 def _get_action_from_changed_transactions(self, transactions, global_scope=False):
     if len(transactions) == 1 and not isinstance(transactions[0], Spawn) \
             and transactions[0] not in self.transactions:
         action = Action(tr('Add transaction'))
         action.added_transactions.add(transactions[0])
     else:
         action = Action(tr('Change transaction'))
         action.change_transactions(transactions)
     if global_scope:
         spawns, txns = extract(lambda x: isinstance(x, Spawn), transactions)
         for schedule in {spawn.recurrence for spawn in spawns}:
             action.change_schedule(schedule)
     return action
예제 #9
0
 def collect_results(collect_all=False):
     # collect results and wait until the queue is small enough to accomodate a new results.
     nonlocal async_results, matches, comparison_count
     limit = 0 if collect_all else RESULTS_QUEUE_LIMIT
     while len(async_results) > limit:
         ready, working = extract(lambda r: r.ready(), async_results)
         for result in ready:
             matches += result.get()
             async_results.remove(result)
             comparison_count += 1
     progress_msg = tr("Performed %d/%d chunk matches") % (
         comparison_count, len(comparisons_to_do))
     j.set_progress(comparison_count, progress_msg)
예제 #10
0
 def collect_results(collect_all=False):
     # collect results and wait until the queue is small enough to accomodate a new results.
     nonlocal async_results, matches, comparison_count, comparisons_to_do
     limit = 0 if collect_all else RESULTS_QUEUE_LIMIT
     while len(async_results) > limit:
         ready, working = extract(lambda r: r.ready(), async_results)
         for result in ready:
             matches += result.get()
             async_results.remove(result)
             comparison_count += 1
     # About the NOQA below: I think there's a bug in pyflakes. To investigate...
     progress_msg = tr("Performed %d/%d chunk matches") % (
         comparison_count,
         len(comparisons_to_do),
     )  # NOQA
     j.set_progress(comparison_count, progress_msg)
예제 #11
0
    def get_spawns(self, end, transactions, consumedtxns):
        """Returns the list of transactions spawned by our budget.

        Works pretty much like :meth:`core.model.recurrence.Recurrence.get_spawns`, except for the
        extra arguments.

        :param transactions: Transactions that can affect our budget spawns' final amount.
        :type transactions: list of :class:`.Transaction`
        :param consumedtxns: Transactions that have already been "consumed" by a budget spawn in
                             this current round of spawning (one a budget "ate" a transaction, we
                             don't have it affect another). This set is going to be mutated
                             (augmented) by this method. All you have to do is start with an empty
                             set and pass it around for each call.
        :type consumedtxns: set of :class:`.Transaction`
        """
        spawns = Recurrence.get_spawns(self, end)
        # No spawn in the past
        spawns = [spawn for spawn in spawns if spawn.date > date.today()]
        account = self.account
        budget_amount = self.amount if account.is_debit_account(
        ) else -self.amount
        relevant_transactions = set(t for t in transactions
                                    if account in t.affected_accounts())
        relevant_transactions -= consumedtxns
        for spawn in spawns:
            affects_spawn = lambda t: spawn.recurrence_date <= t.date <= spawn.date
            wheat, shaft = extract(affects_spawn, relevant_transactions)
            relevant_transactions = shaft
            txns_amount = sum(
                t.amount_for_account(account, budget_amount.currency)
                for t in wheat)
            if abs(txns_amount) < abs(budget_amount):
                spawn_amount = budget_amount - txns_amount
                if spawn.amount_for_account(
                        account, budget_amount.currency) != spawn_amount:
                    spawn.amount = abs(spawn_amount)
                    spawn.set_splits([
                        Split(spawn, account, spawn_amount),
                        Split(spawn, self.target, -spawn_amount)
                    ])
            else:
                spawn.set_splits([])
            consumedtxns |= set(wheat)
        self._previous_spawns = spawns
        return spawns
예제 #12
0
파일: pdf.py 프로젝트: hsoft/pdfmasher
def merge_oneletter_elems(elements):
    # we go through all one-lettered boxes, we check if it intersects with any other rect. In
    # addition, we also check that the bottom-right corner of the letter is in the top-left part
    # of the paragraph.
    # HOWEVER, We must not forget that Y-positions in pdfminer layout is upside down. The bottom of
    # the page is 0 and the top is the max y-pos.
    oneletter, others = extract(lambda e: len(e.text.strip()) == 1, elements)
    for elem1 in oneletter:
        rect = elem1.rect
        for elem2 in others:
            otherrect = elem2.rect
            if rect.intersects(otherrect):
                corner = rect.corners()[1]
                line = Line(otherrect.center(), corner)
                if line.dx() < 0 and line.dy() > 0:
                    elem2.text = elem1.text.strip() + elem2.text
                    elements.remove(elem1)
                    break
예제 #13
0
def merge_oneletter_elems(elements):
    # we go through all one-lettered boxes, we check if it intersects with any other rect. In
    # addition, we also check that the bottom-right corner of the letter is in the top-left part
    # of the paragraph.
    # HOWEVER, We must not forget that Y-positions in pdfminer layout is upside down. The bottom of
    # the page is 0 and the top is the max y-pos.
    oneletter, others = extract(lambda e: len(e.text.strip()) == 1, elements)
    for elem1 in oneletter:
        rect = elem1.rect
        for elem2 in others:
            otherrect = elem2.rect
            if rect.intersects(otherrect):
                corner = rect.corners()[1]
                line = Line(otherrect.center(), corner)
                if line.dx() < 0 and line.dy() > 0:
                    elem2.text = elem1.text.strip() + elem2.text
                    elements.remove(elem1)
                    break
예제 #14
0
    def get_spawns(self, end, transactions, consumedtxns):
        """Returns the list of transactions spawned by our budget.

        Works pretty much like :meth:`core.model.recurrence.Recurrence.get_spawns`, except for the
        extra arguments.

        :param transactions: Transactions that can affect our budget spawns' final amount.
        :type transactions: list of :class:`.Transaction`
        :param consumedtxns: Transactions that have already been "consumed" by a budget spawn in
                             this current round of spawning (one a budget "ate" a transaction, we
                             don't have it affect another). This set is going to be mutated
                             (augmented) by this method. All you have to do is start with an empty
                             set and pass it around for each call.
        :type consumedtxns: set of :class:`.Transaction`
        """
        spawns = Recurrence.get_spawns(self, end)
        # No spawn in the past
        spawns = [spawn for spawn in spawns if spawn.date > date.today()]
        account = self.account
        budget_amount = self.amount if account.is_debit_account() else -self.amount
        relevant_transactions = set(t for t in transactions if account in t.affected_accounts())
        relevant_transactions -= consumedtxns
        for spawn in spawns:
            affects_spawn = lambda t: spawn.recurrence_date <= t.date <= spawn.date
            wheat, shaft = extract(affects_spawn, relevant_transactions)
            relevant_transactions = shaft
            txns_amount = sum(t.amount_for_account(account, budget_amount.currency) for t in wheat)
            if abs(txns_amount) < abs(budget_amount):
                spawn_amount = budget_amount - txns_amount
                if spawn.amount_for_account(account, budget_amount.currency) != spawn_amount:
                    spawn.amount = abs(spawn_amount)
                    spawn.set_splits([Split(spawn, account, spawn_amount), Split(spawn, self.target, -spawn_amount)])
            else:
                spawn.set_splits([])
            consumedtxns |= set(wheat)
        self._previous_spawns = spawns
        return spawns
예제 #15
0
 def delete_transactions(self, transactions, from_account=None):
     action = Action(tr('Remove transaction'))
     spawns, txns = extract(lambda x: isinstance(x, Spawn), transactions)
     global_scope = self._query_for_scope_if_needed(spawns)
     schedules = set(spawn.recurrence for spawn in spawns)
     action.deleted_transactions |= set(txns)
     for schedule in schedules:
         action.change_schedule(schedule)
     self._undoer.record(action)
     
     for txn in transactions:
         if isinstance(txn, Spawn):
             if global_scope:
                 txn.recurrence.stop_before(txn)
             else:
                 txn.recurrence.delete(txn)
         else:
             self.transactions.remove(txn)
     min_date = min(t.date for t in transactions)
     self._cook(from_date=min_date)
     self._clean_empty_categories(from_account=from_account)
     self.notify('transaction_deleted')
     if action.changed_schedules:
         self.notify('schedule_changed')
예제 #16
0
    def draw_pie(self, data, circle_bounds):
        if not data:
            return
        circle_size = min(circle_bounds.w, circle_bounds.h)
        radius = circle_size / 2
        center = circle_bounds.center()

        # draw pie
        total_amount = sum(amount for _, amount, _ in data)
        start_angle = 0
        legends = []
        for legend_text, amount, color_index in data:
            fraction = amount / total_amount
            angle = fraction * 360
            self.view.draw_pie(center, radius, start_angle, angle, color_index)
            legend_angle = start_angle + (angle / 2)
            legend = Legend(text=legend_text,
                            color=color_index,
                            angle=legend_angle)
            legends.append(legend)
            start_angle += angle

        # compute legend rects
        _, legend_height = self.view.text_size('', FontID.Legend)
        for legend in legends:
            legend.base_point = point_in_circle(center, radius, legend.angle)
            legend_width, _ = self.view.text_size(legend.text, FontID.Legend)
            legend.text_rect = rect_from_center(legend.base_point,
                                                (legend_width, legend_height))
            legend.compute_label_rect()

        # make sure they're inside circle_bounds
        for legend in legends:
            pull_rect_in(legend.label_rect, circle_bounds)

        left, right = extract(lambda l: l.base_point.x < center.x, legends)
        # If any legend intersect, we start by sending everyone to their horizontal circle bounds.
        # Then, on each side, if anyone intersect, we go in "Spread mode", spreading all legends
        # vertically in a regular manner.
        if legends_intersect(legends):
            for legend in left:
                legend.label_rect.left = circle_bounds.left
            for legend in right:
                legend.label_rect.right = circle_bounds.right
        for side in (left, right):
            if legends_intersect(side):
                spread_vertically(side, circle_bounds)

        # draw legends
        # draw lines before legends because we don't want them being drawn over other legends
        if len(legends) > 1:
            for legend in legends:
                if not legend.should_draw_line():
                    continue
                self.view.draw_line(legend.label_rect.center(),
                                    legend.base_point, legend.color)
        for legend in legends:
            self.view.draw_rect(legend.label_rect, legend.color,
                                BrushID.Legend)
            legend.compute_text_rect()
            self.view.draw_text(legend.text, legend.text_rect, FontID.Legend)
예제 #17
0
파일: undo.py 프로젝트: prodigeni/moneyguru
 def change_transactions(self, transactions):
     spawns, normal = extract(lambda t: isinstance(t, Spawn), transactions)
     self.changed_transactions |= set((t, t.replicate()) for t in normal)
     for schedule in set(spawn.recurrence for spawn in spawns):
         self.change_schedule(schedule)