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