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