1.1 --- a/imip_manager.py Fri Jan 30 13:30:57 2015 +0100
1.2 +++ b/imip_manager.py Fri Jan 30 16:00:15 2015 +0100
1.3 @@ -614,7 +614,7 @@
1.4 # Record the slots and all time points employed.
1.5
1.6 groups.append(slots)
1.7 - all_points.update([point for point, slot in slots])
1.8 + all_points.update([point for point, t in slots])
1.9
1.10 # Partition the groups into days.
1.11
1.12 @@ -638,7 +638,11 @@
1.13 partitioned = {}
1.14
1.15 for day, day_slots in partition_by_day(slots).items():
1.16 - columns = max(columns, max(map(lambda i: len(i[1]), day_slots)))
1.17 +
1.18 + intervals = []
1.19 + for point, (endpoint, active) in day_slots:
1.20 + columns = max(columns, len(active))
1.21 + intervals.append((point, endpoint))
1.22
1.23 if not days.has_key(day):
1.24 days[day] = set()
1.25 @@ -646,9 +650,11 @@
1.26 # Convert each partition to a mapping from points to active
1.27 # periods.
1.28
1.29 - day_slots = dict(day_slots)
1.30 - partitioned[day] = day_slots
1.31 - days[day].update(day_slots.keys())
1.32 + partitioned[day] = dict(day_slots)
1.33 +
1.34 + # Record the divisions or intervals within each day.
1.35 +
1.36 + days[day].update(intervals)
1.37
1.38 if group_type != "request" or columns:
1.39 group_columns.append(columns)
1.40 @@ -715,7 +721,7 @@
1.41
1.42 # Produce a heading and time points for each day.
1.43
1.44 - for day, points in all_days:
1.45 + for day, intervals in all_days:
1.46 page.thead()
1.47 page.tr()
1.48 page.th(class_="dayheading", colspan=all_columns+1)
1.49 @@ -727,13 +733,13 @@
1.50 groups_for_day = [partitioned.get(day) for partitioned in partitioned_groups]
1.51
1.52 page.tbody()
1.53 - self.show_calendar_points(points, groups_for_day, partitioned_group_types, group_columns)
1.54 + self.show_calendar_points(intervals, groups_for_day, partitioned_group_types, group_columns)
1.55 page.tbody.close()
1.56
1.57 - def show_calendar_points(self, points, groups, group_types, group_columns):
1.58 + def show_calendar_points(self, intervals, groups, group_types, group_columns):
1.59
1.60 """
1.61 - Show the time 'points' along with period information from the given
1.62 + Show the time 'intervals' along with period information from the given
1.63 'groups', having the indicated 'group_types', each with the number of
1.64 columns given by 'group_columns'.
1.65 """
1.66 @@ -742,32 +748,34 @@
1.67
1.68 # Produce a row for each time point.
1.69
1.70 - points = list(points)
1.71 - points.sort()
1.72 + intervals = list(intervals)
1.73 + intervals.sort()
1.74
1.75 - for point in points:
1.76 + for point, endpoint in intervals:
1.77 continuation = point == get_start_of_day(point)
1.78
1.79 page.tr()
1.80 page.th(class_="timeslot")
1.81 - self._time_point(point)
1.82 + self._time_point(point, endpoint)
1.83 page.th.close()
1.84
1.85 # Obtain slots for the time point from each group.
1.86
1.87 for columns, slots, group_type in zip(group_columns, groups, group_types):
1.88 - active = slots and slots.get(point)
1.89 + pt = slots and slots.get(point)
1.90
1.91 # Where no periods exist for the given time interval, generate
1.92 # an empty cell. Where a participant provides no periods at all,
1.93 # the colspan is adjusted to be 1, not 0.
1.94
1.95 - if not active:
1.96 + if not pt or not pt[1]:
1.97 page.td(class_="empty container", colspan=max(columns, 1))
1.98 - self._empty_slot(point)
1.99 + self._empty_slot(point, endpoint)
1.100 page.td.close()
1.101 continue
1.102
1.103 + endpoint, active = pt
1.104 +
1.105 slots = slots.items()
1.106 slots.sort()
1.107 spans = get_spans(slots)
1.108 @@ -819,7 +827,7 @@
1.109 page.td.close()
1.110 else:
1.111 page.td(class_="empty container")
1.112 - self._empty_slot(point)
1.113 + self._empty_slot(point, endpoint)
1.114 page.td.close()
1.115
1.116 # Pad with empty columns.
1.117 @@ -828,21 +836,26 @@
1.118 while i > 0:
1.119 i -= 1
1.120 page.td(class_="empty container")
1.121 - self._empty_slot(point)
1.122 + self._empty_slot(point, endpoint)
1.123 page.td.close()
1.124
1.125 page.tr.close()
1.126
1.127 - def _time_point(self, point):
1.128 + def _time_point(self, point, endpoint):
1.129 + page = self.page
1.130 + value, identifier = self._slot_value_and_identifier(point, endpoint)
1.131 + page.input(name="start", type="radio", value=value, id=identifier, class_="newevent")
1.132 + page.label(self.format_time(point, "long"), class_="timepoint", for_=identifier)
1.133 +
1.134 + def _empty_slot(self, point, endpoint):
1.135 page = self.page
1.136 - pointstr = format_datetime(point)
1.137 - page.input(name="start", type="radio", value=pointstr, id="start-%s" % pointstr, class_="newevent")
1.138 - page.label(self.format_time(point, "long"), class_="timepoint", for_="start-%s" % pointstr)
1.139 + value, identifier = self._slot_value_and_identifier(point, endpoint)
1.140 + page.label("Make a new event in this period", class_="newevent popup", for_=identifier)
1.141
1.142 - def _empty_slot(self, point):
1.143 - page = self.page
1.144 - pointstr = format_datetime(point)
1.145 - page.label("Start a new event at this time", class_="newevent popup", for_="start-%s" % pointstr)
1.146 + def _slot_value_and_identifier(self, point, endpoint):
1.147 + value = "%s-%s" % tuple(map(format_datetime, [point, endpoint]))
1.148 + identifier = "slot-%s" % value
1.149 + return value, identifier
1.150
1.151 def select_action(self):
1.152
2.1 --- a/imiptools/period.py Fri Jan 30 13:30:57 2015 +0100
2.2 +++ b/imiptools/period.py Fri Jan 30 16:00:15 2015 +0100
2.3 @@ -151,9 +151,10 @@
2.4 """
2.5 Return an ordered list of time slots from the given 'scale'.
2.6
2.7 - Each slot is a tuple containing a point in time for the start of the slot,
2.8 - together with a list of parallel event tuples, each tuple containing the
2.9 - original details of an event.
2.10 + Each slot is a tuple of the form (point, (endpoint, active)), where 'point'
2.11 + indicates the start of the slot, 'endpoint' the end of the slot (or None),
2.12 + and 'active' provides a list of parallel event tuples, with each tuple
2.13 + containing the original details of an event.
2.14 """
2.15
2.16 slots = []
2.17 @@ -162,6 +163,10 @@
2.18 points = scale.items()
2.19 points.sort()
2.20
2.21 + # Maintain a current slot so that the end can be added.
2.22 +
2.23 + current = None
2.24 +
2.25 for point, (starting, ending) in points:
2.26
2.27 # Discard all active events ending at or before this start time.
2.28 @@ -186,7 +191,14 @@
2.29 while active and active[-1] is None:
2.30 active.pop()
2.31
2.32 - slots.append((point, active[:]))
2.33 + if current:
2.34 + _point, _active = current
2.35 + slots.append((_point, (point, _active)))
2.36 + current = point, active[:]
2.37 +
2.38 + if current:
2.39 + _point, _active = current
2.40 + slots.append((_point, (None, _active)))
2.41
2.42 return slots
2.43
2.44 @@ -199,22 +211,22 @@
2.45
2.46 new_slots = []
2.47 current_date = None
2.48 - previously_active = None
2.49 + previously_active = []
2.50
2.51 - for point, active in slots:
2.52 + for point, (endpoint, active) in slots:
2.53 start_of_day = get_start_of_day(point)
2.54 this_date = point.date()
2.55
2.56 - # For each new day, add a slot for the start of the day where periods
2.57 - # are active and where no such slot already exists.
2.58 + # For each new day, add a slot for the start of the day where no such
2.59 + # slot already exists.
2.60
2.61 if this_date != current_date:
2.62 current_date = this_date
2.63
2.64 # Add any continuing periods.
2.65
2.66 - if point != start_of_day and previously_active:
2.67 - new_slots.append((start_of_day, previously_active))
2.68 + if point != start_of_day:
2.69 + new_slots.append((start_of_day, (point, previously_active)))
2.70
2.71 # Add the currently active periods at this point in time.
2.72
2.73 @@ -231,31 +243,46 @@
2.74 those added.
2.75 """
2.76
2.77 - new_slots = []
2.78 + for point in points:
2.79 + i = bisect_left(slots, (point, ()))
2.80
2.81 - for point in points:
2.82 - i = bisect_left(slots, (point, None))
2.83 + # Ignore points of time for which slots already exist.
2.84 +
2.85 if i < len(slots) and slots[i][0] == point:
2.86 continue
2.87
2.88 - new_slots.append((point, i > 0 and slots[i-1][1] or []))
2.89 + # Update the endpoint of any preceding slot to refer to this new point.
2.90 +
2.91 + if i > 0:
2.92 + _point, (_endpoint, previously_active) = slots[i-1]
2.93 + slots[i-1] = _point, (point, previously_active)
2.94 + else:
2.95 + previously_active = []
2.96
2.97 - for t in new_slots:
2.98 - insort_left(slots, t)
2.99 + # Either insert a new slot before any following ones.
2.100 +
2.101 + if i < len(slots):
2.102 + _point, (_endpoint, _active) = slots[i]
2.103 + slots.insert(i, (point, (_point, previously_active)))
2.104 +
2.105 + # Or add a new slot at the end of the list.
2.106 +
2.107 + else:
2.108 + slots.append((point, (None, previously_active)))
2.109
2.110 def partition_by_day(slots):
2.111
2.112 """
2.113 - Return a mapping from dates to time points provided by 'slots'.
2.114 + Return a mapping from dates to entries provided by 'slots'.
2.115 """
2.116
2.117 d = {}
2.118
2.119 - for point, value in slots:
2.120 + for point, (endpoint, value) in slots:
2.121 day = point.date()
2.122 if not d.has_key(day):
2.123 d[day] = []
2.124 - d[day].append((point, value))
2.125 + d[day].append((point, (endpoint, value)))
2.126
2.127 return d
2.128
2.129 @@ -263,10 +290,10 @@
2.130
2.131 "Inspect the given 'slots', returning a mapping of event uids to spans."
2.132
2.133 - points = [point for point, active in slots]
2.134 + points = [point for point, (endpoint, active) in slots]
2.135 spans = {}
2.136
2.137 - for point, active in slots:
2.138 + for point, (endpoint, active) in slots:
2.139 for t in active:
2.140 if t and len(t) >= 2:
2.141 start, end, uid, key = get_freebusy_details(t)