Merge branch '2015.11' into 2015.11-develop
[tine20] / tine20 / Calendar / js / MainScreenCenterPanel.js
1 /* 
2  * Tine 2.0
3  * 
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Cornelius Weiss <c.weiss@metaways.de>
6  * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8
9 /* global Ext, Tine */
10
11 Ext.ns('Tine.Calendar');
12
13 Tine.Calendar.MainScreenCenterPanel = Ext.extend(Ext.Panel, {
14     /**
15      * @cfg {String} activeView
16      */
17     activeView: 'weekSheet',
18     
19     startDate: new Date().clearTime(),
20     
21     /**
22      * $property Object view -> startdate
23      */
24     startDates: null,
25     
26     /**
27      * @cfg {String} loadMaskText
28      * _('Loading events, please wait...')
29      */
30     loadMaskText: 'Loading events, please wait...',
31     
32     /**
33      * @cfg {Number} autoRefreshInterval (seconds)
34      */
35     autoRefreshInterval: 300,
36     
37     /**
38      * @property autoRefreshTask
39      * @type Ext.util.DelayedTask
40      */
41     autoRefreshTask: null,
42     
43     /**
44      * add records from other applications using the split add button
45      * - activated by default
46      * 
47      * @type Bool
48      * @property splitAddButton
49      */
50     splitAddButton: true,
51     
52     /**
53      * default print mode
54      * 
55      * @type String {sheet|grid}
56      * @property defaultPrintMode
57      */
58     defaultPrintMode: 'sheet',
59     
60     periodRe: /^(day|week|month|year)/i,
61     presentationRe: /(sheet|grid)$/i,
62     
63     calendarPanels: {},
64     
65     border: false,
66     layout: 'border',
67     
68     stateful: true,
69     stateId: 'cal-mainscreen',
70     stateEvents: ['changeview'],
71     
72     getState: function () {
73         return Ext.copyTo({}, this, 'activeView');
74     },
75     
76     applyState: Ext.emptyFn,
77     
78     initComponent: function () {
79         var me = this;
80         
81         this.addEvents(
82         /**
83          * @event changeview
84          * fired if an event got clicked
85          * @param {Tine.Calendar.MainScreenCenterPanel} mspanel
86          * @param {String} view
87          */
88         'changeview');
89         
90         this.recordClass = Tine.Calendar.Model.Event;
91         
92         this.app = Tine.Tinebase.appMgr.get('Calendar');
93         
94         // init some translations
95         this.i18nRecordName = this.app.i18n.n_hidden(this.recordClass.getMeta('recordName'), this.recordClass.getMeta('recordsName'), 1);
96         this.i18nRecordsName = this.app.i18n._hidden(this.recordClass.getMeta('recordsName'));
97         this.i18nContainerName = this.app.i18n.n_hidden(this.recordClass.getMeta('containerName'), this.recordClass.getMeta('containersName'), 1);
98         this.i18nContainersName = this.app.i18n._hidden(this.recordClass.getMeta('containersName'));
99         
100         this.loadMaskText = this.app.i18n._hidden(this.loadMaskText);
101         
102         var state = Ext.state.Manager.get(this.stateId, {});
103         Ext.apply(this, state);
104         
105         this.defaultFilters = [
106             {field: 'attender', operator: 'in', value: [Ext.apply(Tine.Calendar.Model.Attender.getDefaultData(), {
107                 user_id: Tine.Tinebase.registry.get('currentAccount')
108             })]},
109             {field: 'attender_status', operator: 'notin', value: ['DECLINED']}
110         ];
111         this.filterToolbar = this.getFilterToolbar({
112             onFilterChange: this.refresh.createDelegate(this, [false]),
113             getAllFilterData: this.getAllFilterData.createDelegate(this)
114         });
115         
116         this.filterToolbar.getQuickFilterPlugin().criteriaIgnores.push(
117             {field: 'period'},
118             {field: 'grants'}
119         );
120         
121         this.startDates = [];
122         this.initActions();
123         this.initLayout();
124         
125         // init autoRefresh
126         this.autoRefreshTask = new Ext.util.DelayedTask(this.refresh, this, [{
127             refresh: true,
128             autoRefresh: true
129         }]);
130         
131         Tine.Calendar.MainScreenCenterPanel.superclass.initComponent.call(this);
132     },
133     
134     initActions: function () {
135         this.action_editInNewWindow = new Ext.Action({
136             requiredGrant: 'readGrant',
137             text: this.i18nEditActionText ? this.app.i18n._hidden(this.i18nEditActionText) : String.format(Tine.Tinebase.translation._hidden('Edit {0}'), this.i18nRecordName),
138             disabled: true,
139             handler: this.onEditInNewWindow.createDelegate(this, ["edit"]),
140             iconCls: 'action_edit'
141         });
142         
143         this.action_addInNewWindow = new Ext.Action({
144             requiredGrant: 'addGrant',
145             text: this.i18nAddActionText ? this.app.i18n._hidden(this.i18nAddActionText) : String.format(Tine.Tinebase.translation._hidden('Add {0}'), this.i18nRecordName),
146             handler: this.onEditInNewWindow.createDelegate(this, ["add"]),
147             iconCls: 'action_add'
148         });
149         
150         this.action_cut = new Ext.Action({
151             requiredGrant: 'deleteGrant',
152             text: this.app.i18n._('Cut event'),
153             handler: this.onCutEvent.createDelegate(this),
154             iconCls: 'action_cut'
155         });
156
157         this.action_copy_to = new Ext.Action({
158             requiredGrant: 'deleteGrant',
159             text: this.app.i18n._('Copy Event to clipboard'),
160             handler: this.onCopyToEvent.createDelegate(this),
161             iconCls: 'action_editcopy'
162         });
163         
164         this.action_cancelPasting = new Ext.Action({
165             requiredGrant: 'deleteGrant',
166             text: this.app.i18n._('Stop cut / copy & paste'),
167             handler: this.onCutCancelEvent.createDelegate(this),
168             iconCls: 'action_cut_break'
169         });
170         
171         // note: unprecise plural form here, but this is hard to change
172         this.action_deleteRecord = new Ext.Action({
173             requiredGrant: 'deleteGrant',
174             allowMultiple: true,
175             singularText: this.i18nDeleteActionText ? i18nDeleteActionText[0] : String.format(Tine.Tinebase.translation.n_hidden('Delete {0}', 'Delete {0}', 1), this.i18nRecordName),
176             pluralText: this.i18nDeleteActionText ? i18nDeleteActionText[1] : String.format(Tine.Tinebase.translation.n_hidden('Delete {0}', 'Delete {0}', 1), this.i18nRecordsName),
177             translationObject: this.i18nDeleteActionText ? this.app.i18n : Tine.Tinebase.translation,
178             text: this.i18nDeleteActionText ? this.i18nDeleteActionText[0] : String.format(Tine.Tinebase.translation.n_hidden('Delete {0}', 'Delete {0}', 1), this.i18nRecordName),
179             handler: this.onDeleteRecords,
180             disabled: true,
181             iconCls: 'action_delete',
182             scope: this
183         });
184         
185         this.actions_print = new Ext.Action({
186             requiredGrant: 'readGrant',
187             text: this.app.i18n._('Print Page'),
188             handler: this.onPrint.createDelegate(this, []),
189             iconCls:'action_print',
190             scope: this,
191             listeners: {
192                 arrowclick: this.onPrintMenuClick.createDelegate(this)
193             },
194             menu:{
195                 items:[{
196                     text: this.app.i18n._('Grid'),
197                     iconCls: 'cal-grid-view-type',
198                     handler: this.onPrint.createDelegate(this, ['grid'])
199                 }, {
200                     text: this.app.i18n._('Sheet'),
201                     iconCls: 'cal-week-view',
202                     handler: this.onPrint.createDelegate(this, ['sheet']),
203                     disabled: Ext.isIE || Ext.isNewIE
204                 }]
205             }
206         });
207         
208         this.showSheetView = new Ext.Button({
209             pressed: this.isActiveView('Sheet'),
210             scale: 'medium',
211             minWidth: 60,
212             rowspan: 2,
213             iconAlign: 'top',
214             requiredGrant: 'readGrant',
215             text: this.app.i18n._('Sheet'),
216             handler: this.changeView.createDelegate(this, ["sheet"]),
217             iconCls:'cal-sheet-view-type',
218             xtype: 'tbbtnlockedtoggle',
219             toggleGroup: 'Calendar_Toolbar_tgViewTypes',
220             scope: this
221         });
222         
223         this.showGridView = new Ext.Button({
224             pressed: this.isActiveView('Grid'),
225             scale: 'medium',
226             minWidth: 60,
227             rowspan: 2,
228             iconAlign: 'top',
229             requiredGrant: 'readGrant',
230             text: this.app.i18n._('Grid'),
231             handler: this.changeView.createDelegate(this, ["grid"]),
232             iconCls:'cal-grid-view-type',
233             xtype: 'tbbtnlockedtoggle',
234             toggleGroup: 'Calendar_Toolbar_tgViewTypes',
235             scope: this
236         });
237
238         this.showDayView = new Ext.Toolbar.Button({
239             pressed: this.isActiveView('Day'),
240             text: this.app.i18n._('Day'),
241             iconCls: 'cal-day-view',
242             xtype: 'tbbtnlockedtoggle',
243             handler: this.changeView.createDelegate(this, ["day"]),
244             enableToggle: true,
245             toggleGroup: 'Calendar_Toolbar_tgViews'
246         });
247         this.showWeekView = new Ext.Toolbar.Button({
248             pressed: this.isActiveView('Week'),
249             text: this.app.i18n._('Week'),
250             iconCls: 'cal-week-view',
251             xtype: 'tbbtnlockedtoggle',
252             handler: this.changeView.createDelegate(this, ["week"]),
253             enableToggle: true,
254             toggleGroup: 'Calendar_Toolbar_tgViews'
255         });
256         this.showMonthView = new Ext.Toolbar.Button({
257             pressed: this.isActiveView('Month'),
258             text: this.app.i18n._('Month'),
259             iconCls: 'cal-month-view',
260             xtype: 'tbbtnlockedtoggle',
261             handler: this.changeView.createDelegate(this, ["month"]),
262             enableToggle: true,
263             toggleGroup: 'Calendar_Toolbar_tgViews'
264         });
265         this.showYearView = new Ext.Toolbar.Button({
266             pressed: String(this.activeView).match(/^year/i),
267             text: this.app.i18n._('Year'),
268             iconCls: 'cal-year-view',
269             xtype: 'tbbtnlockedtoggle',
270             handler: this.changeView.createDelegate(this, ["year"]),
271             enableToggle: true,
272             toggleGroup: 'Calendar_Toolbar_tgViews'
273         });
274         
275        this.action_import = new Ext.Action({
276             requiredGrant: 'addGrant',
277             text: this.app.i18n._('Import Events'),
278             disabled: false,
279             handler: this.onImport,
280             minWidth: 60,
281             requiredGrant: 'readGrant',
282             iconCls: 'action_import',
283             scope: this,
284             allowMultiple: true
285         });
286
287         this.action_export = new Tine.Calendar.ExportButton({
288             text: this.app.i18n._('Export Events'),
289             iconCls: 'action_export',
290             exportFunction: 'Calendar.exportEvents',
291             showExportDialog: true,
292             gridPanel: this
293         });
294
295         this.changeViewActions = [
296             this.showDayView,
297             this.showWeekView,
298             this.showMonthView
299         ];
300
301         if (this.app.featureEnabled('featureYearView')) {
302             this.changeViewActions.push(this.showYearView);
303         }
304
305         this.recordActions = [
306             this.action_editInNewWindow,
307             this.action_deleteRecord
308         ];
309         
310         this.actionUpdater = new  Tine.widgets.ActionUpdater({
311             actions: this.recordActions,
312             grantsProperty: false,
313             containerProperty: false
314         });
315     },
316
317     /**
318      * get current view type (Grid/Day/Week/...)
319      *
320      * @param {String} view
321      * @returns {Boolean}
322      */
323     isActiveView: function(view)
324     {
325         switch (view) {
326             case 'Grid':
327                 return String(this.activeView).match(/grid$/i);
328                 break;
329             case 'Day':
330                 return String(this.activeView).match(/day/i);
331                 break;
332             case 'Week':
333                 return String(this.activeView).match(/week/i);
334                 break;
335             case 'Month':
336                 return String(this.activeView).match(/month/i);
337                 break;
338             case 'Sheet':
339                 return String(this.activeView).match(/sheet/i);
340                 break;
341             default:
342                 return false;
343         }
344     },
345     
346     /**
347      * returns the paste action
348      * 
349      * @param {Date} datetime
350      * @param {Tine.Calendar.Model.Event} event
351      */
352     getPasteAction: function(datetime, event) {
353         var shortSummary = Ext.util.Format.ellipsis(event.get('summary'), 15);
354         return new Ext.Action({
355             requiredGrant: 'addGrant',
356             text: String.format(this.app.i18n._('Paste event "{0}"'), shortSummary),
357             handler: this.onPasteEvent.createDelegate(this, [datetime]),
358             iconCls: 'action_paste'
359         });
360     },
361         
362     getActionToolbar: Tine.widgets.grid.GridPanel.prototype.getActionToolbar,
363     
364     getActionToolbarItems: function() {
365         return [{
366             xtype: 'buttongroup',
367             columns: 1,
368             rows: 2,
369             frame: false,
370             items: [
371                 this.action_import,
372                 this.action_export
373             ]
374         }, {
375             xtype: 'buttongroup',
376             plugins: [{
377                 ptype: 'ux.itemregistry',
378                 key:   'Calendar-MainScreenPanel-ViewBtnGrp'
379             }],
380             items: [
381                 this.showSheetView,
382                 this.showGridView
383             ]
384         }];
385     },
386     
387     /**
388      * @private
389      * 
390      * NOTE: Order of items matters! Ext.Layout.Border.SplitRegion.layout() does not
391      *       fence the rendering correctly, as such it's impotant, so have the ftb
392      *       defined after all other layout items
393      */
394     initLayout: function () {
395         this.items = [{
396             region: 'center',
397             layout: 'card',
398             activeItem: 0,
399             border: false,
400             items: [this.getCalendarPanel(this.activeView)]
401         }];
402         
403         // add detail panel
404         if (this.detailsPanel) {
405             this.items.push({
406                 region: 'south',
407                 border: false,
408                 collapsible: true,
409                 collapseMode: 'mini',
410                 header: false,
411                 split: true,
412                 layout: 'fit',
413                 height: this.detailsPanel.defaultHeight ? this.detailsPanel.defaultHeight : 125,
414                 items: this.detailsPanel
415                 
416             });
417             //this.detailsPanel.doBind(this.activeView);
418         }
419         
420         // add filter toolbar
421         if (this.filterToolbar) {
422             this.items.push({
423                 region: 'north',
424                 border: false,
425                 items: this.filterToolbar,
426                 listeners: {
427                     scope: this,
428                     afterlayout: function (ct) {
429                         ct.suspendEvents();
430                         ct.setHeight(this.filterToolbar.getHeight());
431                         ct.ownerCt.layout.layout();
432                         ct.resumeEvents();
433                     }
434                 }
435             });
436         }
437     },
438     
439     /**
440      * import events
441      * 
442      * @param {Button} btn 
443      */
444     onImport: function(btn) {
445         var popupWindow = Tine.Calendar.ImportDialog.openWindow({
446             appName: 'Calendar',
447             modelName: 'Event',
448             defaultImportContainer: this.app.getMainScreen().getWestPanel().getContainerTreePanel().getDefaultContainer('defaultContainer'),
449             
450             ignoreConflicts: true,
451             doTryRun: false,
452             
453             
454             // update grid after import
455             listeners: {
456                 scope: this,
457                 'finish': function() {
458                     this.refresh();
459                 }
460             }
461         });
462     },
463     
464     /**
465      * @private
466      */
467     onRender: function(ct, position) {
468         Tine.Calendar.MainScreenCenterPanel.superclass.onRender.apply(this, arguments);
469         
470         var defaultFavorite = Tine.widgets.persistentfilter.model.PersistentFilter.getDefaultFavorite(this.app.appName, this.recordClass.prototype.modelName),
471             favoritesPanel  = this.app.getMainScreen().getWestPanel().getFavoritesPanel();
472         
473         this.loadMask = new Ext.LoadMask(this.body, {msg: this.loadMaskText});
474         
475         if (defaultFavorite) {
476             favoritesPanel.selectFilter(defaultFavorite);
477         } else {
478             this.refresh();
479         }
480     },
481     
482     getViewParts: function (view) {
483         view = String(view);
484         
485         var activeView = String(this.activeView),
486             periodMatch = view.match(this.periodRe),
487             period = Ext.isArray(periodMatch) ? periodMatch[0] : null,
488             activePeriodMatch = activeView.match(this.periodRe),
489             activePeriod = Ext.isArray(activePeriodMatch) ? activePeriodMatch[0] : 'week',
490             presentationMatch = view.match(this.presentationRe),
491             presentation = Ext.isArray(presentationMatch) ? presentationMatch[0] : null,
492             activePresentationMatch = activeView.match(this.presentationRe),
493             activePresentation = Ext.isArray(activePresentationMatch) ? activePresentationMatch[0] : 'sheet';
494             
495         return {
496             period: period ? period : activePeriod,
497             presentation: presentation ? presentation : activePresentation,
498             toString: function() {return this.period + Ext.util.Format.capitalize(this.presentation);}
499         };
500     },
501     
502     changeView: function (view, startDate) {
503         // autocomplete view
504         var viewParts = this.getViewParts(view);
505         view = viewParts.toString();
506         
507         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::changeView(' + view + ',' + startDate + ')');
508         
509         // save current startDate
510         this.startDates[this.activeView] = this.startDate.clone();
511         
512         if (startDate && Ext.isDate(startDate)) {
513             this.startDate = startDate.clone();
514         } else {
515             // see if a recent startDate of that view fits
516             var lastStartDate = this.startDates[view],
517                 currentPeriod = this.getCalendarPanel(this.activeView).getView().getPeriod();
518                 
519             if (Ext.isDate(lastStartDate) && lastStartDate.between(currentPeriod.from, currentPeriod.until)) {
520                 this.startDate = this.startDates[view].clone();
521             }
522         }
523         
524         var panel = this.getCalendarPanel(view);
525         var cardPanel = this.items.first();
526         
527         if (panel.rendered) {
528             cardPanel.layout.setActiveItem(panel.id);
529         } else {
530             cardPanel.add(panel);
531             cardPanel.layout.setActiveItem(panel.id);
532             cardPanel.doLayout();
533         }
534         
535         this.activeView = view;
536         
537         // move around changeViewButtons
538         var rightRow = Ext.get(Ext.DomQuery.selectNode('tr[class=x-toolbar-right-row]', panel.tbar.dom));
539         
540         for (var i = this.changeViewActions.length - 1; i >= 0; i--) {
541             rightRow.insertFirst(this.changeViewActions[i].getEl().parent().dom);
542         }
543         this['show' + Ext.util.Format.capitalize(viewParts.period) +  'View'].toggle(true);
544         this['show' + Ext.util.Format.capitalize(viewParts.presentation) +  'View'].toggle(true);
545         
546         // update actions
547         this.updateEventActions();
548         
549         // update data
550         panel.getView().updatePeriod({from: this.startDate});
551         panel.getStore().load({});
552         
553         this.fireEvent('changeview', this, view);
554     },
555
556     /**
557      * returns all filter data for current view
558      */
559     getAllFilterData: function (options) {
560         var store = this.getCalendarPanel(this.activeView).getStore();
561         
562         options = Ext.apply({
563             refresh: true, // ommit loadMask
564             noPeriodFilter: true
565         }, options || {});
566         this.onStoreBeforeload(store, options);
567         
568         return options.params.filter;
569     },
570     
571     getCustomfieldFilters: Tine.widgets.grid.GridPanel.prototype.getCustomfieldFilters,
572     
573     getFilterToolbar: Tine.widgets.grid.GridPanel.prototype.getFilterToolbar,
574     
575     /**
576      * returns store of currently active view
577      */
578     getStore: function () {
579         return this.getCalendarPanel(this.activeView).getStore();
580     },
581     
582     /**
583      * onContextMenu
584      * 
585      * @param {Event} e
586      */
587     onContextMenu: function (e) {
588         e.stopEvent();
589
590         var view = this.getCalendarPanel(this.activeView).getView();
591         var event = view.getTargetEvent(e);
592         var datetime = view.getTargetDateTime(e);
593         
594         if (event && event.id.match(/new-ext-gen/) ) {
595             // new event, no ctx menu possible atm
596             // @see 0008948: deactivate context menu on new events
597             return;
598         }
599         
600         var addAction, responseAction, copyAction;
601
602         if (datetime || event) {
603             var dtStart = datetime || event.get('dtstart').clone();
604             if (dtStart.format('H:i') === '00:00') {
605                 dtStart = dtStart.add(Date.HOUR, 9);
606             }
607             
608             addAction = {
609                 text: this.i18nAddActionText ? this.app.i18n._hidden(this.i18nAddActionText) : String.format(Tine.Tinebase.translation._hidden('Add {0}'), this.i18nRecordName),
610                 handler: this.onEditInNewWindow.createDelegate(this, ["add", null, null, {dtStart: dtStart, is_all_day_event: datetime && datetime.is_all_day_event}]),
611                 iconCls: 'action_add'
612             };
613             
614             // assemble event actions
615             if (event) {
616                 responseAction = this.getResponseAction(event);
617                 copyAction = this.getCopyAction(event);
618             }
619         } else {
620             addAction = this.action_addInNewWindow;
621         }
622         
623         if (event) {
624             view.getSelectionModel().select(event, e, e.ctrlKey);
625         } else {
626             view.getSelectionModel().clearSelections();
627         }
628
629         var menuitems = this.recordActions.concat(addAction, responseAction || [], copyAction || []);
630         
631         if (event) {
632             this.action_copy_to.setDisabled(event.isRecurInstance() || event.isRecurException() || event.isRecurBase());
633             menuitems = menuitems.concat(['-', this.action_cut, this.action_copy_to, '-']);
634         } else if (Tine.Tinebase.data.Clipboard.has('Calendar', 'Event')) {
635             menuitems = menuitems.concat(['-', this.getPasteAction(datetime, Tine.Tinebase.data.Clipboard.pull('Calendar', 'Event', true)), this.action_cancelPasting, '-']);
636         }
637
638         var ctxMenu = new Ext.menu.Menu({
639             plugins: [{
640                 ptype: 'ux.itemregistry',
641                 key:   'Calendar-MainScreenPanel-ContextMenu',
642                 config: {
643                     event: event,
644                     datetime: datetime
645                 }
646             },
647             // to allow gridpanel hooks (like email compose)
648             {
649                 ptype: 'ux.itemregistry',
650                 key:   'Calendar-Event-GridPanel-ContextMenu'
651             }],
652             items: menuitems
653         });
654         ctxMenu.showAt(e.getXY());
655     },
656     
657     /**
658      * get response action
659      * 
660      * @param {Tine.Calendar.Model.Event} event
661      * @return {Object}
662      */
663     getResponseAction: function(event) {
664         var statusStore = Tine.Tinebase.widgets.keyfield.StoreMgr.get('Calendar', 'attendeeStatus'),
665             myAttenderRecord = event.getMyAttenderRecord(),
666             myAttenderStatus = myAttenderRecord ? myAttenderRecord.get('status') : null,
667             myAttenderStatusRecord = statusStore.getById(myAttenderStatus),
668             responseAction = null;
669         
670         if (myAttenderRecord) {
671             responseAction = {
672                 text: this.app.i18n._('Set my response'),
673                 icon: myAttenderStatusRecord ? myAttenderStatusRecord.get('icon') : false,
674                 menu: []
675             };
676             
677             statusStore.each(function(status) {
678                 var isCurrent = myAttenderRecord.get('status') === status.id;
679                 
680                 // NOTE: we can't use checked items here as we use icons already
681                 responseAction.menu.push({
682                     text: status.get('i18nValue'),
683                     handler: this.setResponseStatus.createDelegate(this, [event, status.id]),
684                     icon: status.get('icon'),
685                     disabled: myAttenderRecord.get('status') === status.id
686                 });
687             }, this);
688         }
689         
690         return responseAction;
691     },
692
693     /**
694      * get copy action
695      * 
696      * @param {Tine.Calendar.Model.Event} event
697      * @return {Object}
698      */
699     getCopyAction: function(event) {
700         var copyAction = {
701             text: String.format(this.app.i18n._('Copy {0}'), this.i18nRecordName),
702             handler: this.onEditInNewWindow.createDelegate(this, ["copy", event]),
703             iconCls: 'action_editcopy',
704             // TODO allow to copy recurring events / exceptions
705             disabled: event.isRecurInstance() || event.isRecurException() || event.isRecurBase()
706         };
707         
708         return copyAction
709     },
710     
711     checkPastEvent: function(event, checkBusyConflicts, actionType, oldEvent) {
712         var start = event.get('dtstart').getTime();
713         var morning = new Date().clearTime().getTime();
714
715         switch (actionType) {
716             case 'update':
717                 var title = this.app.i18n._('Updating event in the past'),
718                     optionYes = this.app.i18n._('Update this event'),
719                     optionNo = this.app.i18n._('Do not update this event');
720                 break;
721             case 'add':
722             default:
723                 var title = this.app.i18n._('Creating event in the past'),
724                     optionYes = this.app.i18n._('Create this event'),
725                     optionNo = this.app.i18n._('Do not create this event');
726         }
727         
728         if(start < morning) {
729             Tine.widgets.dialog.MultiOptionsDialog.openWindow({
730                 title: title,
731                 height: 170,
732                 scope: this,
733                 options: [
734                     {text: optionYes, name: 'yes'},
735                     {text: optionNo, name: 'no'}
736                 ],
737                 
738                 handler: function(option) {
739                     try {
740                         switch (option) {
741                             case 'yes':
742                                 if (actionType == 'update') this.onUpdateEvent(event, true, oldEvent);
743                                 else this.onAddEvent(event, checkBusyConflicts, true);
744                                 break;
745                             case 'no':
746                             default:
747                                 try {
748                                     var panel = this.getCalendarPanel(this.activeView);
749                                     if(panel) {
750                                         var store = panel.getStore(),
751                                             view = panel.getView();
752                                     }
753                                 } catch(e) {
754                                     var panel = null, 
755                                         store = null,
756                                         view = null;
757                                 }
758                                 
759                                 if (actionType == 'add') {
760                                     if(store) store.remove(event);
761                                 } else {
762                                     if (view && view.rendered) {
763                                         this.loadMask.show();
764                                         store.reload();
765 //                                        TODO: restore original event so no reload is needed
766 //                                        var updatedEvent = event;
767 //                                        updatedEvent.dirty = false;
768 //                                        updatedEvent.editing = false;
769 //                                        updatedEvent.modified = null;
770 //                                        
771 //                                        store.replaceRecord(event, updatedEvent);
772 //                                        view.getSelectionModel().select(event);
773                                     }
774                                     this.setLoading(false);
775                                 }
776                         }
777                     } catch (e) {
778                         Tine.log.error('Tine.Calendar.MainScreenCenterPanel::checkPastEvent::handler');
779                         Tine.log.error(e);
780                     }
781                 }             
782             });
783         } else {
784             if (actionType == 'update') this.onUpdateEvent(event, true, oldEvent);
785             else this.onAddEvent(event, checkBusyConflicts, true);
786         }
787     },
788     
789     onAddEvent: function(event, checkBusyConflicts, pastChecked) {
790         if(!pastChecked) {
791             this.checkPastEvent(event, checkBusyConflicts, 'add');
792             return;
793         }
794         
795         this.setLoading(true);
796         
797         // remove temporary id
798         if (event.get('id').match(/new/)) {
799             event.set('id', '');
800         }
801         
802         if (event.isRecurBase()) {
803             if (this.loadMask) {
804                 this.loadMask.show();
805             }
806         }
807         
808         var panel = this.getCalendarPanel(this.activeView),
809             store = event.store,
810             view = panel.getView();
811         
812         Tine.Calendar.backend.saveRecord(event, {
813             scope: this,
814             success: function(createdEvent) {
815                 if (createdEvent.isRecurBase()) {
816                     store.load({refresh: true});
817                 } else {
818                     // store may be lost on conflict or else
819                     if (store) {
820                         store.replaceRecord(event, createdEvent);
821                         this.setLoading(false);
822                     } else {
823                         this.refresh();
824                     }
825                     
826                     if (view && view.rendered) {
827                         view.getSelectionModel().select(createdEvent);
828                     }
829                 }
830             },
831             failure: this.onProxyFail.createDelegate(this, [event], true)
832         }, {
833             checkBusyConflicts: checkBusyConflicts === false ? 0 : 1
834         });
835     },
836     
837     onUpdateEvent: function(event, pastChecked, oldEvent) {
838         this.setLoading(true);
839         
840         if(!pastChecked) {
841             this.checkPastEvent(event, null, 'update', oldEvent);
842             return;
843         }
844         
845         if (event.isRecurBase() && this.loadMask) {
846            this.loadMask.show();
847         }
848         
849         if (event.id && (event.isRecurInstance() || event.isRecurException() || (event.isRecurBase() && ! event.get('rrule').newrule))) {
850             
851             var options = [];
852             
853             // the container has changed - force update whole series
854 //            if (! oldEvent || event.get('container_id') == oldEvent.get('container_id')) {
855                 options.push({text: this.app.i18n._('Update this event only'), name: 'this'});
856                 options.push({text: this.app.i18n._('Update this and all future events'), name: (event.isRecurBase() && ! event.get('rrule').newrule) ? 'series' : 'future'});
857 //            }
858             
859             options.push({text: this.app.i18n._('Update whole series'), name: 'series'});
860             options.push({text: this.app.i18n._('Update nothing'), name: 'cancel'});
861             
862             Tine.widgets.dialog.MultiOptionsDialog.openWindow({
863                 title: this.app.i18n._('Update Event'),
864                 height: 170,
865                 scope: this,
866                 options: options,
867                 handler: function(option) {
868                     var store = event.store;
869
870                     if (! store) {
871                         store = this.getStore();
872                     }
873                     
874                     switch (option) {
875                         case 'series':
876                             if (this.loadMask) {
877                                 this.loadMask.show();
878                             }
879                             
880                             var options = {
881                                 scope: this,
882                                 success: function() {
883                                     store.load({refresh: true});
884                                 }
885                             };
886                             options.failure = this.onProxyFail.createDelegate(this, [event, Tine.Calendar.backend.updateRecurSeries.createDelegate(Tine.Calendar.backend, [event, false, options])], true);
887                             
888                             if (event.isRecurException()) {
889                                 Tine.Calendar.backend.saveRecord(event, options, {range: 'ALL', checkBusyConflicts: true});
890                             } else {
891                                 Tine.Calendar.backend.updateRecurSeries(event, true, options);
892                             }
893                             break;
894                             
895                         case 'this':
896                         case 'future':
897                             var options = {
898                                 scope: this,
899                                 success: function(updatedEvent) {
900                                     // NOTE: we also need to refresh for 'this' as otherwise
901                                     //       baseevent is not refreshed -> concurrency failures
902                                     store.load({refresh: true});
903                                 },
904                                 failure: this.onProxyFail.createDelegate(this, [event], true)
905                             };
906                             
907                             
908                             if (event.isRecurException()) {
909                                 var range = (option === 'this' ? 'THIS' : 'THISANDFUTURE');
910                                 options.failure = this.onProxyFail.createDelegate(this, [event, Tine.Calendar.backend.saveRecord.createDelegate(Tine.Calendar.backend, [event, options, {range: range, checkBusyConflicts: false}])], true);
911                                 Tine.Calendar.backend.saveRecord(event, options, {range: range, checkBusyConflicts: true});
912                             } else {
913                                 options.failure = this.onProxyFail.createDelegate(this, [event, Tine.Calendar.backend.createRecurException.createDelegate(Tine.Calendar.backend, [event, false, option == 'future', false, options])], true);
914                                 Tine.Calendar.backend.createRecurException(event, false, option === 'future', true, options);
915                             }
916                             break;
917                             
918                         default:
919                             if(this.loadMask) { //no loadMask called from another app
920                                 this.loadMask.show();
921                                 store.load({refresh: true});
922                             }
923                             break;
924                     }
925
926                 } 
927             });
928         } else {
929             this.onUpdateEventAction(event);
930         }
931     },
932     
933     onUpdateEventAction: function(event) {
934         var panel = this.getCalendarPanel(this.activeView),
935             store = panel.getStore(),
936             view = panel.getView();
937             
938         Tine.Calendar.backend.saveRecord(event, {
939             scope: this,
940             success: function(updatedEvent) {
941                 if (updatedEvent.isRecurBase()) {
942                     store.load({refresh: true});
943                 } else {
944                     this.congruenceFilterCheck(event, updatedEvent);
945                 }
946             },
947             failure: this.onProxyFail.createDelegate(this, [event], true)
948         }, {
949             checkBusyConflicts: 1
950         });
951     },
952
953     /**
954      * checks, if the last filter still matches after update
955      * 
956      * @param {Tine.Calendar.Model.Event} event
957      * @param {Tine.Calendar.Model.Event} updatedEvent
958      */
959     congruenceFilterCheck: function(event, updatedEvent) {
960         if (! (event.ui && event.ui.rendered)) {
961             event.store.replaceRecord(event, updatedEvent);
962             this.setLoading(false);
963             return;
964         }
965         
966         var filterData = this.getAllFilterData(),
967             store = event.store;
968         
969         if (! store) {
970             store = this.getStore();
971         }
972         
973         filterData[0].filters[0].filters.push({field: 'id', operator: 'in', value: [ updatedEvent.get('id') ]});
974         
975         Tine.Calendar.searchEvents(filterData, {}, function(r) {
976             if(r.totalcount == 0) {
977                 updatedEvent.outOfFilter = true;
978             }
979             
980             store.replaceRecord(event, updatedEvent);
981             this.setLoading(false);
982         }, this);
983     },
984     
985     onDeleteRecords: function () {
986         var panel = this.getCalendarPanel(this.activeView);
987         var selection = panel.getSelectionModel().getSelectedEvents();
988         
989         var containsRecurBase = false,
990             containsRecurInstance = false,
991             containsRecurException = false;
992         
993         Ext.each(selection, function (event) {
994             if(event.ui) event.ui.markDirty();
995             if (event.isRecurInstance()) {
996                 containsRecurInstance = true;
997             }
998             if (event.isRecurBase()) {
999                 containsRecurBase = true;
1000             }
1001             if (event.isRecurException()) {
1002                 containsRecurException = true;
1003             }
1004         });
1005         
1006         if (selection.length > 1 && (containsRecurBase || containsRecurInstance)) {
1007             Ext.Msg.show({
1008                 title: this.app.i18n._('Please Change Selection'), 
1009                 msg: this.app.i18n._('Your selection contains recurring events. Recuring events must be deleted seperatly!'),
1010                 icon: Ext.MessageBox.INFO,
1011                 buttons: Ext.Msg.OK,
1012                 scope: this,
1013                 fn: function () {
1014                     this.onDeleteRecordsConfirmFail(panel, selection);
1015                 }
1016             });
1017             return;
1018         }
1019         
1020         if (selection.length === 1 && (containsRecurBase || containsRecurInstance || containsRecurException)) {
1021             this.deleteMethodWin = Tine.widgets.dialog.MultiOptionsDialog.openWindow({
1022                 title: this.app.i18n._('Delete Event'),
1023                 scope: this,
1024                 height: 170,
1025                 options: [
1026                     {text: this.app.i18n._('Delete this event only'), name: 'this'},
1027                     {text: this.app.i18n._('Delete this and all future events'), name: containsRecurBase ? 'all' : 'future'},
1028                     {text: this.app.i18n._('Delete whole series'), name: 'all'},
1029                     {text: this.app.i18n._('Delete nothing'), name: 'nothing'}
1030                 ],
1031                 handler: function (option) {
1032                     try {
1033                         switch (option) {
1034                             case 'all':
1035                             case 'this':
1036                             case 'future':
1037                                 panel.getTopToolbar().beforeLoad();
1038                                 if (option !== 'this') {
1039                                     this.loadMask.show();
1040                                 }
1041                                 
1042                                 var options = {
1043                                     scope: this,
1044                                     success: function () {
1045                                         if (option === 'this') {
1046                                             Ext.each(selection, function (event) {
1047                                                 panel.getStore().remove(event);
1048                                             });
1049                                         }
1050                                         this.refresh(true);
1051                                     }
1052                                 };
1053                                 
1054                                 if (containsRecurException) {
1055                                     var range = (option === 'future') ? 'THISANDFUTURE' : Ext.util.Format.uppercase(option);
1056                                     Tine.Calendar.backend.deleteRecords([selection[0]], options, {range: range});
1057                                 } else {
1058                                     if (option === 'all') {
1059                                         Tine.Calendar.backend.deleteRecurSeries(selection[0], options);
1060                                     } else {
1061                                         Tine.Calendar.backend.createRecurException(selection[0], true, option === 'future', false, options);
1062                                     }
1063                                 }
1064                                 break;
1065                             default:
1066                                 this.onDeleteRecordsConfirmFail(panel, selection);
1067                                 break;
1068                     }
1069                     
1070                     } catch (e) {
1071                         Tine.log.error('Tine.Calendar.MainScreenCenterPanel::onDeleteRecords::handle');
1072                         Tine.log.error(e);
1073                     }
1074                 }
1075             });
1076             return;
1077         }
1078         
1079         // else
1080         var i18nQuestion = String.format(this.app.i18n.ngettext('Do you really want to delete this event?', 'Do you really want to delete the {0} selected events?', selection.length), selection.length);
1081         Ext.MessageBox.confirm(Tine.Tinebase.translation._hidden('Confirm'), i18nQuestion, function (btn) {
1082             if (btn === 'yes') {
1083                 this.onDeleteRecordsConfirmNonRecur(panel, selection);
1084             } else {
1085                 this.onDeleteRecordsConfirmFail(panel, selection);
1086             }
1087         }, this);
1088         
1089     },
1090     
1091     onDeleteRecordsConfirmNonRecur: function (panel, selection) {
1092         panel.getTopToolbar().beforeLoad();
1093         
1094         // create a copy of selection so selection changes don't affect this
1095         var sel = Ext.unique(selection);
1096                 
1097         var options = {
1098             scope: this,
1099             success: function () {
1100                 panel.getTopToolbar().onLoad();
1101                 Ext.each(sel, function (event) {
1102                     panel.getStore().remove(event);
1103                 });
1104             },
1105             failure: function () {
1106                 panel.getTopToolbar().onLoad();
1107                 Ext.MessageBox.alert(Tine.Tinebase.translation._hidden('Failed'), String.format(this.app.i18n.n_('Failed to delete event', 'Failed to delete the {0} events', selection.length), selection.length));
1108             }
1109         };
1110         
1111         Tine.Calendar.backend.deleteRecords(selection, options);
1112     },
1113     
1114     onDeleteRecordsConfirmFail: function (panel, selection) {
1115         Ext.each(selection, function (event) {
1116             event.ui.clearDirty();
1117         });
1118     },
1119     
1120     /**
1121      * is called on action cut
1122      * 
1123      * @param {} action
1124      * @param {} event
1125      */
1126     onCutEvent: function(action, event) {
1127         var panel = this.getCalendarPanel(this.activeView);
1128         var selection = panel.getSelectionModel().getSelectedEvents();
1129         if (Ext.isArray(selection) && selection.length === 1) {
1130             event = selection[0];
1131         }
1132         if (event.ui) {
1133             event.ui.markDirty();
1134         }
1135         Tine.Tinebase.data.Clipboard.push(event);
1136     },
1137
1138     /**
1139      * Is called on copy to clipboard
1140      *
1141      * @param action
1142      * @param event
1143      */
1144     onCopyToEvent: function(action, event) {
1145         var panel = this.getCalendarPanel(this.activeView);
1146         var selection = panel.getSelectionModel().getSelectedEvents();
1147         if (Ext.isArray(selection) && selection.length === 1) {
1148             event = selection[0];
1149         }
1150
1151         event.isCopy = true;
1152
1153         Tine.Tinebase.data.Clipboard.push(event);
1154     },
1155     
1156     /**
1157      * is called on cancelling cut & paste
1158      */
1159     onCutCancelEvent: function() {
1160         var store = this.getStore();
1161         var ids = Tine.Tinebase.data.Clipboard.getIds('Calendar', 'Event');
1162         
1163         for (var index = 0; index < ids.length; index++) {
1164             var record = store.getAt(store.findExact('id', ids[index]));
1165             if (record && record.ui) {
1166                 record.ui.clearDirty();
1167             }
1168         }
1169
1170         Tine.Tinebase.data.Clipboard.clear('Calendar', 'Event');
1171     },
1172     
1173     /**
1174      * is called on action paste
1175      * 
1176      * @param {Date} datetime
1177      */
1178     onPasteEvent: function(datetime) {
1179         var record = Tine.Tinebase.data.Clipboard.pull('Calendar', 'Event');
1180         var isCopy = record.isCopy;
1181
1182         if (! record) {
1183             return;
1184         }
1185         
1186         var dtend   = record.get('dtend');
1187         var dtstart = record.get('dtstart');
1188         var eventLength = dtend - dtstart;
1189
1190         if (isCopy != true) {
1191             // remove before update
1192             var store = this.getStore();
1193             var oldRecord = store.getAt(store.findExact('id', record.getId()));
1194             if (oldRecord && oldRecord.hasOwnProperty('ui')) {
1195                 oldRecord.ui.remove();
1196             }
1197         } else {
1198             record = Tine.Calendar.EventEditDialog.superclass.doCopyRecordToReturn.call(this, record);
1199
1200             record.set('editGrant', true);
1201             record.set('id', '');
1202
1203             // remove attender ids
1204             Ext.each(record.data.attendee, function(attender) {
1205                 delete attender.id;
1206             }, this);
1207         }
1208
1209         // Allow to change attendee if in split view
1210         var defaultAttendee = Tine.Calendar.Model.Event.getDefaultData().attendee;
1211         if (record.data.attendee[0].user_id.account_id != defaultAttendee[0].user_id.account_id) {
1212             record.data.attendee[0] = Tine.Calendar.Model.Event.getDefaultData().attendee[0];
1213         }
1214
1215         record.set('dtstart', datetime);
1216         record.set('dtend', new Date(datetime.getTime() + eventLength));
1217
1218         if (isCopy == true) {
1219             record.isCopy = true;
1220             Tine.Tinebase.data.Clipboard.push(record);
1221             if (record.ui) {
1222                 record.ui.clearDirty();
1223             }
1224
1225             this.onAddEvent(record);
1226         } else {
1227             this.onUpdateEvent(record);
1228         }
1229     },
1230     
1231     /**
1232      * open event in new window
1233      * 
1234      * @param {String} action  add|edit|copy
1235      * @param {String} event   edit given event instead of selected event
1236      * @param {Object} plugins event dlg plugins
1237      * @param {Object} default properties for new items
1238      */
1239     onEditInNewWindow: function (action, event, plugins, defaults) {
1240         if (! event) {
1241             event = null;
1242         }
1243         // needed for addToEventPanel
1244         if (Ext.isObject(action)) {
1245             action = action.actionType;
1246         }
1247         
1248         if (action === 'edit' && ! event) {
1249             var panel = this.getCalendarPanel(this.activeView);
1250             var selection = panel.getSelectionModel().getSelectedEvents();
1251             if (Ext.isArray(selection) && selection.length === 1) {
1252                 event = selection[0];
1253             }
1254         }
1255
1256         if (action === 'edit' && (! event || event.dirty)) {
1257             return;
1258         }
1259         
1260         if (! event) {
1261             event = new Tine.Calendar.Model.Event(Ext.apply(Tine.Calendar.Model.Event.getDefaultData(), defaults), 0);
1262             if (defaults && Ext.isDate(defaults.dtStart)) {
1263                 event.set('dtstart', defaults.dtStart);
1264                 event.set('dtend', defaults.dtStart.add(Date.MINUTE, Tine.Calendar.Model.Event.getMeta('defaultEventDuration')));
1265             }
1266         }
1267         
1268         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::onEditInNewWindow() - Opening event edit dialog with action: ' + action);
1269
1270         Tine.Calendar.EventEditDialog.openWindow({
1271             plugins: plugins ? Ext.encode(plugins) : null,
1272             record: Ext.encode(event.data),
1273             recordId: event.data.id,
1274             copyRecord: (action == 'copy'),
1275             listeners: {
1276                 scope: this,
1277                 update: function (eventJson) {
1278                     var updatedEvent = Tine.Calendar.backend.recordReader({responseText: eventJson});
1279                     
1280                     updatedEvent.dirty = true;
1281                     updatedEvent.modified = {};
1282                     event.phantom = (action === 'edit');
1283                     var panel = this.getCalendarPanel(this.activeView);
1284                     var store = panel.getStore();
1285                     event = store.getById(event.id);
1286                     
1287                     if (event && action !== 'copy') {
1288                         store.replaceRecord(event, updatedEvent);
1289                     } else {
1290                         store.add(updatedEvent);
1291                     }
1292                     
1293                     this.onUpdateEvent(updatedEvent, false, event);
1294                 }
1295             }
1296         });
1297     },
1298     
1299     onKeyDown: function (e) {
1300         if (e.ctrlKey) {
1301             switch (e.getKey()) 
1302             {
1303             case e.A:
1304                 // select only current page
1305                 //this.grid.getSelectionModel().selectAll(true);
1306                 e.preventDefault();
1307                 break;
1308             case e.E:
1309                 if (!this.action_editInNewWindow.isDisabled()) {
1310                     this.onEditInNewWindow('edit');
1311                 }
1312                 e.preventDefault();
1313                 break;
1314             case e.N:
1315                 if (!this.action_addInNewWindow.isDisabled()) {
1316                     this.onEditInNewWindow('add');
1317                 }
1318                 e.preventDefault();
1319                 break;
1320             }
1321         } else if (e.getKey() === e.DELETE) {
1322             if (! this.action_deleteRecord.isDisabled()) {
1323                 this.onDeleteRecords.call(this);
1324             }
1325         }
1326     },
1327     
1328     onPrintMenuClick: function(splitBtn, e) {
1329         if (! String(this.activeView).match(/(day|week)sheet$/i)) {
1330             splitBtn.menu.hide();
1331         }
1332     },
1333     
1334     onPrint: function(printMode) {
1335         var printMode = printMode ? printMode : this.defaultPrintMode,
1336             panel = this.getCalendarPanel(this.activeView),
1337             view = panel ? panel.getView() : null;
1338             
1339         if (view && Ext.isFunction(view.print)) {
1340             view.print(printMode);
1341         } else {
1342             Ext.Msg.alert(this.app.i18n._('Could not Print'), this.app.i18n._('Sorry, your current view does not support printing.'));
1343         }
1344     },
1345     
1346     /**
1347      * called before store queries for data
1348      */
1349     onStoreBeforeload: function (store, options) {
1350         options.params = options.params || {};
1351         
1352         // define a transaction
1353         this.lastStoreTransactionId = options.transactionId = Ext.id();
1354         
1355         // allways start with an empty filter set!
1356         // this is important for paging and sort header!
1357         options.params.filter = [];
1358         
1359         if (! options.refresh && this.rendered) {
1360             // defer to have the loadMask centered in case of rendering actions
1361             this.loadMask.show.defer(50, this.loadMask);
1362         }
1363         
1364         // note, we can't use the 'normal' plugin approach here, cause we have to deal with n stores
1365         //var calendarSelectionPlugin = this.app.getMainScreen().getWestPanel().getContainerTreePanel().getFilterPlugin();
1366         //calendarSelectionPlugin.onBeforeLoad.call(calendarSelectionPlugin, store, options);
1367         
1368         this.filterToolbar.onBeforeLoad.call(this.filterToolbar, store, options);
1369         
1370         // add period filter as last filter to not conflict with first OR filter
1371         if (! options.noPeriodFilter) {
1372             options.params.filter.push({field: 'period', operator: 'within', value: this.getCalendarPanel(this.activeView).getView().getPeriod() });
1373         }
1374     },
1375     
1376     /**
1377      * fence against loading of wrong data set
1378      */
1379     onStoreBeforeLoadRecords: function(o, options, success) {
1380         return this.lastStoreTransactionId === options.transactionId;
1381     },
1382     
1383     /**
1384      * called when store loaded data
1385      */
1386     onStoreLoad: function (store, options) {
1387         if (this.rendered) {
1388             this.loadMask.hide();
1389         }
1390         
1391         // reset autoRefresh
1392         if (window.isMainWindow && this.autoRefreshInterval) {
1393             this.autoRefreshTask.delay(this.autoRefreshInterval * 1000);
1394         }
1395         
1396         // check if store is current store
1397         if (store !== this.getCalendarPanel(this.activeView).getStore()) {
1398             Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::onStoreLoad view is not active anymore');
1399             return;
1400         }
1401         
1402         if (this.rendered) {
1403             // update filtertoolbar
1404             if(this.filterToolbar) {
1405                 this.filterToolbar.setValue(store.proxy.jsonReader.jsonData.filter);
1406             }
1407             // update container tree
1408             Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getWestPanel().getContainerTreePanel().getFilterPlugin().setValue(store.proxy.jsonReader.jsonData.filter);
1409             
1410             // update attendee filter grid
1411             Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getWestPanel().getAttendeeFilter().setFilterValue(store.proxy.jsonReader.jsonData.filter);
1412         }
1413     },
1414     
1415     /**
1416      * on store load exception
1417      * 
1418      * @param {Tine.Tinebase.data.RecordProxy} proxy
1419      * @param {String} type
1420      * @param {Object} error
1421      * @param {Object} options
1422      */
1423     onStoreLoadException: function(proxy, type, error, options) {
1424         // reset autoRefresh
1425         if (window.isMainWindow && this.autoRefreshInterval) {
1426             this.autoRefreshTask.delay(this.autoRefreshInterval * 5000);
1427         }
1428         
1429         this.setLoading(false);
1430         this.loadMask.hide();
1431         
1432         if (! options.autoRefresh) {
1433             this.onProxyFail(error);
1434         }
1435     },
1436     
1437     onProxyFail: function(error, event, ignoreFn) {
1438         this.setLoading(false);
1439         if(this.loadMask) this.loadMask.hide();
1440         
1441         if (error.code == 901) {
1442            
1443             // resort fbInfo to combine all events of a attender
1444             var busyAttendee = [];
1445             var conflictEvents = {};
1446             var attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(error.event.attendee);
1447             
1448             Ext.each(error.freebusyinfo, function(fbinfo) {
1449                 attendeeStore.each(function(a) {
1450                     var userType = a.get('user_type');
1451                     userType = userType == 'groupmember' ? 'user' : userType;
1452                     if (userType == fbinfo.user_type && a.getUserId() == fbinfo.user_id) {
1453                         if (busyAttendee.indexOf(a) < 0) {
1454                             busyAttendee.push(a);
1455                             conflictEvents[a.id] = [];
1456                         }
1457                         conflictEvents[a.id].push(fbinfo);
1458                     }
1459                 });
1460             }, this);
1461             
1462             // generate html for each busy attender
1463             var busyAttendeeHTML = '';
1464             var denyIgnore = false;
1465
1466             Ext.each(busyAttendee, function(busyAttender) {
1467                 // TODO refactore name handling of attendee
1468                 //      -> attender model needs knowlege of how to get names!
1469                 //var attenderName = a.getName();
1470                 var attenderName = Tine.Calendar.AttendeeGridPanel.prototype.renderAttenderName.call(Tine.Calendar.AttendeeGridPanel.prototype, busyAttender.get('user_id'), false, busyAttender);
1471                 busyAttendeeHTML += '<div class="cal-conflict-attendername">' + attenderName + '</div>';
1472                 
1473                 var eventInfos = [];
1474                 Ext.each(conflictEvents[busyAttender.id], function(fbInfo) {
1475                     var format = 'H:i';
1476                     var dateFormat = Ext.form.DateField.prototype.format;
1477                     if (event.get('dtstart').format(dateFormat) != event.get('dtend').format(dateFormat) ||
1478                         Date.parseDate(fbInfo.dtstart, Date.patterns.ISO8601Long).format(dateFormat) != Date.parseDate(fbInfo.dtend, Date.patterns.ISO8601Long).format(dateFormat))
1479                     {
1480                         format = dateFormat + ' ' + format;
1481                     }
1482                     
1483                     var eventInfo = Date.parseDate(fbInfo.dtstart, Date.patterns.ISO8601Long).format(format) + ' - ' + Date.parseDate(fbInfo.dtend, Date.patterns.ISO8601Long).format(format);
1484                     if (fbInfo.event && fbInfo.event.summary) {
1485                         eventInfo += ' : ' + fbInfo.event.summary;
1486                     }
1487                     if (fbInfo.type == 'BUSY_UNAVAILABLE') {
1488                         denyIgnore = true;
1489                         eventInfo += '<span class="cal-conflict-eventinfos-unavailable">' + this.app.i18n._('Unavailable') + '</span>';
1490                     }
1491                     eventInfos.push(eventInfo);
1492                 }, this);
1493                 busyAttendeeHTML += '<div class="cal-conflict-eventinfos">' + eventInfos.join(', <br />') + '</div>';
1494                 
1495             }, this);
1496             
1497             this.conflictConfirmWin = Tine.widgets.dialog.MultiOptionsDialog.openWindow({
1498                 modal: true,
1499                 allowCancel: false,
1500                 height: 180 + 15*error.freebusyinfo.length,
1501                 title: this.app.i18n._('Scheduling Conflict'),
1502                 questionText: '<div class = "cal-conflict-heading">' +
1503                                    this.app.i18n._('The following attendee are busy at the requested time:') + 
1504                                '</div>' +
1505                                busyAttendeeHTML,
1506                 options: [
1507                     {text: this.app.i18n._('Ignore Conflict'), name: 'ignore', disabled: denyIgnore},
1508                     {text: this.app.i18n._('Edit Event'), name: 'edit', checked: true},
1509                     {text: this.app.i18n._('Cancel this action'), name: 'cancel'}
1510                 ],
1511                 scope: this,
1512                 handler: function(option) {
1513                     var panel = this.getCalendarPanel(this.activeView),
1514                         store = panel.getStore();
1515
1516                     switch (option) {
1517                         case 'ignore':
1518                             if (Ext.isFunction(ignoreFn)) {
1519                                 ignoreFn();
1520                             } else {
1521                                 this.onAddEvent(event, false, true);
1522                             }
1523                             this.conflictConfirmWin.close();
1524                             break;
1525                         
1526                         case 'edit':
1527                             
1528                             var presentationMatch = this.activeView.match(this.presentationRe),
1529                                 presentation = Ext.isArray(presentationMatch) ? presentationMatch[0] : null;
1530                             
1531                             if (presentation != 'Grid') {
1532                                 var view = panel.getView();
1533                                 view.getSelectionModel().select(event);
1534                                 // mark event as not dirty to allow edit dlg
1535                                 event.dirty = false;
1536                                 view.fireEvent('dblclick', view, event);
1537                             } else {
1538                                 // add or edit?
1539                                 this.onEditInNewWindow(null, event);
1540                             }
1541                             
1542                             this.conflictConfirmWin.close();
1543                             break;
1544                         case 'cancel':
1545                         default:
1546                             this.conflictConfirmWin.close();
1547                             this.loadMask.show();
1548                             store.load({refresh: true});
1549                             break;
1550                     }
1551                 }
1552             });
1553             
1554         } else {
1555             Tine.Tinebase.ExceptionHandler.handleRequestException(error);
1556         }
1557     },
1558     
1559     refresh: function (options) {
1560         // convert old boolean argument
1561         options = Ext.isObject(options) ? options : {
1562             refresh: !!options
1563         };
1564         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::refresh(' + options.refresh + ')');
1565         var panel = this.getCalendarPanel(this.activeView);
1566         panel.getStore().load(options);
1567         
1568         // clear favorites
1569         Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getWestPanel().getFavoritesPanel().getSelectionModel().clearSelections();
1570     },
1571     
1572     setLoading: function(bool) {
1573         if (this.rendered) {
1574             var panel = this.getCalendarPanel(this.activeView),
1575                 tbar = panel.getTopToolbar();
1576                 
1577             if (tbar && tbar.loading) {
1578                 tbar.loading[bool ? 'disable' : 'enable']();
1579             }
1580         }
1581     },
1582     
1583     setResponseStatus: function(event, status) {
1584         var myAttenderRecord = event.getMyAttenderRecord();
1585         if (myAttenderRecord) {
1586             myAttenderRecord.set('status', status);
1587             event.dirty = true;
1588             event.modified = {};
1589             
1590             var panel = this.getCalendarPanel(this.activeView);
1591             var store = panel.getStore();
1592             
1593             store.replaceRecord(event, event);
1594             
1595             this.onUpdateEvent(event);
1596         }
1597     },
1598     
1599     updateEventActions: function () {
1600         var panel = this.getCalendarPanel(this.activeView);
1601         var selection = panel.getSelectionModel().getSelectedEvents();
1602         
1603         this.actionUpdater.updateActions(selection);
1604         if (this.detailsPanel) {
1605             this.detailsPanel.onDetailsUpdate(panel.getSelectionModel());
1606         }
1607     },
1608     
1609     updateView: function (which) {
1610         Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::updateView(' + which + ')');
1611         var panel = this.getCalendarPanel(which);
1612         var period = panel.getTopToolbar().getPeriod();
1613         
1614         panel.getView().updatePeriod(period);
1615         panel.getStore().load({});
1616         //this.updateMiniCal();
1617     },
1618     
1619     /**
1620      * returns requested CalendarPanel
1621      * 
1622      * @param {String} which
1623      * @return {Tine.Calendar.CalendarPanel}
1624      */
1625     getCalendarPanel: function (which) {
1626         var whichParts = this.getViewParts(which);
1627         which = whichParts.toString();
1628         
1629         if (! this.calendarPanels[which]) {
1630             Tine.log.debug('Tine.Calendar.MainScreenCenterPanel::getCalendarPanel creating new calender panel for view ' + which);
1631             
1632             var store = new Ext.data.JsonStore({
1633                 //autoLoad: true,
1634                 id: 'id',
1635                 fields: Tine.Calendar.Model.Event,
1636                 proxy: Tine.Calendar.backend,
1637                 reader: new Ext.data.JsonReader({}), //Tine.Calendar.backend.getReader(),
1638                 listeners: {
1639                     scope: this,
1640                     'beforeload': this.onStoreBeforeload,
1641                     'beforeloadrecords' : this.onStoreBeforeLoadRecords,
1642                     'load': this.onStoreLoad,
1643                     'loadexception': this.onStoreLoadException
1644                 },
1645                 replaceRecord: function(o, n) {
1646                     var idx = this.indexOf(o);
1647                     this.remove(o);
1648                     this.insert(idx, n);
1649                 }
1650             });
1651             
1652             var tbar = new Tine.Calendar.PagingToolbar({
1653                 view: whichParts.period,
1654                 store: store,
1655                 dtStart: this.startDate,
1656                 listeners: {
1657                     scope: this,
1658                     // NOTE: only render the button once for the toolbars
1659                     //       the buttons will be moved on chageView later
1660                     render: function (tbar) {
1661                         for (var i = 0; i < this.changeViewActions.length; i += 1) {
1662                             if (! this.changeViewActions[i].rendered) {
1663                                 tbar.addButton(this.changeViewActions[i]);
1664                             }
1665                         }
1666                     }
1667                 }
1668             });
1669             
1670             tbar.on('change', this.updateView.createDelegate(this, [which]), this, {buffer: 200});
1671             tbar.on('refresh', this.refresh.createDelegate(this, [true]), this, {buffer: 200});
1672             
1673             if (whichParts.presentation.match(/sheet/i)) {
1674                 var view;
1675                 switch (which) {
1676                     case 'daySheet':
1677                         view = new Tine.Calendar.DaysView({
1678                             store: store,
1679                             startDate: tbar.getPeriod().from,
1680                             numOfDays: 1
1681                         });
1682                         break;
1683                     case 'monthSheet':
1684                         view = new Tine.Calendar.MonthView({
1685                             store: store,
1686                             period: tbar.getPeriod()
1687                         });
1688                         break;
1689                     case 'yearSheet':
1690                         view = new Tine.Calendar.YearView({
1691                             store: store,
1692                             period: tbar.getPeriod()
1693                         });
1694                         break;
1695                     default:
1696                     case 'weekSheet':
1697                         view = new Tine.Calendar.DaysView({
1698                             store: store,
1699                             startDate: tbar.getPeriod().from,
1700                             numOfDays: 7
1701                         });
1702                         break;
1703                 }
1704                 
1705                 view.on('changeView', this.changeView, this);
1706                 view.on('changePeriod', function (period) {
1707                     this.startDate = period.from;
1708                     this.startDates[which] = this.startDate.clone();
1709                     this.updateMiniCal();
1710                 }, this);
1711                 
1712                 // quick add/update actions
1713                 view.on('addEvent', this.onAddEvent, this);
1714                 view.on('updateEvent', this.onUpdateEvent, this);
1715             
1716                 this.calendarPanels[which] = new Tine.Calendar.CalendarPanel({
1717                     tbar: tbar,
1718                     view: view,
1719                     mainScreen: this
1720                 });
1721             } else if (whichParts.presentation.match(/grid/i)) {
1722                 this.calendarPanels[which] = new Tine.Calendar.GridView({
1723                     tbar: tbar,
1724                     store: store
1725                 });
1726             }
1727             
1728             this.calendarPanels[which].on('dblclick', this.onEditInNewWindow.createDelegate(this, ["edit"]));
1729             this.calendarPanels[which].on('contextmenu', this.onContextMenu, this);
1730             this.calendarPanels[which].getSelectionModel().on('selectionchange', this.updateEventActions, this);
1731             this.calendarPanels[which].on('keydown', this.onKeyDown, this);
1732             
1733             this.calendarPanels[which].relayEvents(this, ['show', 'beforehide']);
1734         }
1735         
1736         return this.calendarPanels[which];
1737     },
1738     
1739     updateMiniCal: function () {
1740         var miniCal = Ext.getCmp('cal-mainscreen-minical');
1741         var weekNumbers = null;
1742         var period = this.getCalendarPanel(this.activeView).getView().getPeriod();
1743         
1744         switch (this.activeView) 
1745         {
1746         case 'week' :
1747             weekNumbers = [period.from.add(Date.DAY, 1).getWeekOfYear()];
1748             break;
1749         case 'month' :
1750             weekNumbers = [];
1751             var startWeek = period.from.add(Date.DAY, 1).getWeekOfYear();
1752             var numWeeks = Math.round((period.until.getTime() - period.from.getTime()) / Date.msWEEK);
1753             for (var i = 0; i < numWeeks; i += 1) {
1754                 weekNumbers.push(startWeek + i);
1755             }
1756             break;
1757         }
1758         miniCal.update(this.startDate, true, weekNumbers);
1759     }
1760 });