[EMAIL PROTECTED] (Russell Steinthal) writes:
> In case it's not clear from the Bugzilla exchange, my goal is to
> allow the calendar views to merge data from more than one calendar
> source: most commonly two or more iCalendar files, but there's no
> reason it shouldn't also work for any other backends which end up
> getting written. The user would have the options to Include
> Calendar... and Exclude Calendar... (or something like that) from the
> UI, and the Views would update accordingly.
>
> My initial thought was that this would be relatively simple: instead
> of a 1:1 relationship between the view and the CalClient object,
> give views a list of clients, create a hash table mapping items
> (events/todos) in the view to the proper client, and then simply
> route the view/client calls appropriately (and iterating over all the
> clients when needed, for example on opening). Conceptually, I still
> think that's the best approach.
>
> My problem is that the CalClient pointers aren't just confined to the
> view (I've been looking at EDayView to start). There's also
> CalQuery, GnomeCalendar, and I'm sure a few others which I found and
> can't think of at the moment. To change them all would be a
> relatively massive patch, and prone to errors.
Russell, thanks for working on this!
Okay, let me explain how the whole thing works. Forgive me if this is
too verbose, but people who are not familiar with the way the
Evolution Calendar works may find this useful.
All the interfaces to the personal calendar server (PCS) in the Wombat
are wrapped by C libraries so that you do not have to fiddle with
CORBA and the IDL interface directly.
The main interface to the calendar is thus the CalClient class. A
CalClient provides a handle to an open calendar. You create a
CalClient object, ask it to open a calendar, and it will contact the
PCS and give you back a handle to the calendar you requested. Opening
calendars happens asynchronously, so some time after you issue the
cal_client_open_calendar() request you will get notification via the
"cal_opened" signal.
The Evolution calendar works in model/view fashion. The GUI side is
just a collection of dumb views that do not store the data by
themselves; they do not deal with storing the appointments and all
that. Rather, they reflect the state of the loaded calendar(s) in the
PCS. When an appointment or other type of calendar component changes
in the PCS because someone modified it, all the CalClient objects will
get notification about that and they will emit the "obj_updated"
signal. The GUI views connect to this signal and are responsible for
displaying the changed object. Also, when an object is deleted in the
PCS you will get the "obj_removed" signal.
You can ask a CalClient to give you a list of calendar components that
occur within a specified range of time, and other miscellaneous tasks.
Older versions of the calendar simply created a CalClient object,
opened your default calendar, and then told the views to use that
client. The views would get all notifications directly from it. They
would do cal_client_get_objects_in_range() or
cal_client_generate_instances() to get the calendar components that
occur in their displayed range of time. Then they would watch for the
obj_updated and obj_removed signals to update the display
appropriately.
Then we added some code so that the PCS could do live search queries.
This is basically searching functionality for the calendar. Instead
of simply having a
UID_List cal_client_gimme_objects_that_match (client, search_criteria)
we have a more elegant mechanism through which you create a CalQuery
object:
CalQuery cal_client_get_query (client, search_criteria)
In effect, a CalQuery is magic stuff that filters the notifications
from the PCS so that you will only get "obj_updated" and "obj_removed"
signals from it when an object matches your query or no longer
matches.
The search criteria are provided as an S-expression. We have nice
predicates like occur-in-range? and has-categories?. So if you wanted
a view to have the events from today until the next week, you could
provide an expression like
(and (= (get-vtype) "VEVENT")
(occur-in-range? (time-now)
(time-add-day (time-now) 7)))
If you wanted all the tasks that have the category "business" you
could say
(and (= (get-vtype) "VTODO")
(has-category? "business"))
And if you want next month's events that have "my butt" in the
description,
(and (= (get-vtype) "VEVENT")
(occur-in-range? (time-now)
(time-add-day (time-now) 31))
(contains? "description" "my butt"))
When you create the CalQuery, the PCS will go through the objects in
the calendar looking for those that match your criteria. When it
finds an object that matches, the CalQuery will emit the "obj_updated"
signal. It will emit the "query_done" signal when it runs out of
objects.
Now, the interesting thing is that the CalQuery remains alive until
you destroy it. If new objects get added to the calendar and they
happen to match the sexp from a live CalQuery, that CalQuery will also
emit the "obj_updated" signal. If an object used to match a
particular query and it gets changed so that it no longer matches, or
if the object is deleted, then the CalQuery will emit the
"obj_removed" signal.
The PCS keeps track of all the live CalQueries that you have running
and will notify all of them as appropriate. So the calendar views now
watch CalQuery objects that they rebuild from time to time, instead of
watching the CalClient directly.
Watching a CalClient directly is equivalent to creating a query that
matches
#t
i.e. "true", or all objects match regardless of their contents.
The views destroy and recreate their respective CalQueries, for
example, when you change the displayed time range. The day view
regenerates its query when you switch days. Views also regenerate
their queries when you type something in the Quick Search bar. The
quick search bar generates a simple expression like
(has-category? "fun")
and the views tweak it to match their particular needs. The day view
would tweak it to be something like
(and (has-category? "fun")
(= (get-vtype) "VEVENT")
(occur-in-time-range? selected-day (+ selected-day 1)))
i.e. it adds predicates that make sense for the day view. The task
list would add a predicate for (= (get-vtype) "VTODO"), etc.
You create a CalQuery object by using cal_client_get_query(), but
after that the query is not associated to that CalClient object in any
way. This is because it is all done server-side; you just need the
CalClient so that you can say "please create a query for this open
calendar (the CalClient)".
The way to make views display more than one calendar is as follows.
All the views store a single CalClient. This should be replaced with
a list of something like
struct LoadedCalendar {
CalClient *client;
CalQuery *query;
any_information_you_need_to_paint_the_events_for_that_calendar
}
So for each open calendar you need a CalClient, a CalQuery to filter
which objects to display, and any extra information you need to
display the objects for that particular calendar (colors, icons,
whatever).
Of course you then need methods to add and remove calendars from a
view.
The views need a concept of a "current calendar", which is the
calendar that the user is currently editing. You may have ten
calendars displayed on a single view, but when the user creates a new
appointment, to which calendar do you add it? That is the current
calendar. Of course, changing existing objects presents no problem
since you already know their parent calendar.
Views keep structures like
struct FooViewEvent {
/* The component */
CalComponent *comp;
/* Occurrence times */
time_t occur_start;
time_t occur_end;
/* View-specific information */
GnomeCanvasItem *blah;
...
};
You would need to add a field to point to the CalClient to which the
object belongs, or perhaps the LoadedCalendar structure above.
When you need to paint the objects, you color-code them or whatever.
I guess you would present the user with a color picker in the "add
calendar to views" dialog. Or something. Display icons, whatever.
You need to give the user a way of picking the "current calendar" as
explained above. Instead of showing a list of URIs we should allow
the user to name the calendars so that you get a list with
My personal calendar
Company calendar
Concert calendar from www.yourlocalsymphonyorchestra.org
Movie schedule from www.cinemex.com.mx
I don't know if you want to specify a different search query for each
one of the loaded calendars. It may make the interface simpler if you
only allow the same query to be applied to all calendars; but then it
doesn't make sense to filter for the "meetings" category, related to
your business/company calendar, when you also have the Movies calendar
loaded... take your pick.
So, it is not very hard to do. You just keep a list of open calendars
in the views and associate the calendar components with the CalClient
to which they belong. All the rest is miscellaneous support code for
the user interface. The PCS needs exactly zero changes; it is all
client-side stuff.
> [1] Ok, it's mostly aspirational. I am determined to have my
> contribution to Evolution 1.0 *not* be a patch removing my name from
> the About box. Hence this message... :)
Dude, you did massive amounts of cool work for Gnomecal, and then it
became the Evolution calendar. If you remove yourself I will slap you
with a curried trout and add you again :)
Federico
_______________________________________________
evolution-hackers maillist - [EMAIL PROTECTED]
http://lists.helixcode.com/mailman/listinfo/evolution-hackers