aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
blob: 5a8ab36bc1ed96ee290f35e489d823c82b6579e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
/*
 * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
 * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.sufficientlysecure.keychain.ui;

import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.Fragment;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment;
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter;
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder;
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener;
import org.sufficientlysecure.keychain.util.Preferences;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class SettingsKeyserverFragment extends Fragment implements RecyclerItemClickListener.OnItemClickListener {

    private static final String ARG_KEYSERVER_ARRAY = "arg_keyserver_array";
    private ItemTouchHelper mItemTouchHelper;

    private ArrayList<String> mKeyservers;
    private KeyserverListAdapter mAdapter;

    public static SettingsKeyserverFragment newInstance(String[] keyservers) {
        Bundle args = new Bundle();
        args.putStringArray(ARG_KEYSERVER_ARRAY, keyservers);

        SettingsKeyserverFragment fragment = new SettingsKeyserverFragment();
        fragment.setArguments(args);

        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
            savedInstanceState) {

        return inflater.inflate(R.layout.settings_keyserver_fragment, null);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        String keyservers[] = getArguments().getStringArray(ARG_KEYSERVER_ARRAY);
        mKeyservers = new ArrayList<>(Arrays.asList(keyservers));

        mAdapter = new KeyserverListAdapter(mKeyservers);

        RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.keyserver_recycler_view);
        // recyclerView.setHasFixedSize(true); // the size of the first item changes
        recyclerView.setAdapter(mAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));


        ItemTouchHelper.Callback callback = new ItemTouchHelperDragCallback(mAdapter);
        mItemTouchHelper = new ItemTouchHelper(callback);
        mItemTouchHelper.attachToRecyclerView(recyclerView);

        // for clicks
        recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), this));

        // can't use item decoration because it doesn't move with drag and drop
        // recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null));

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true);
    }

    @Override
    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
        inflater.inflate(R.menu.keyserver_pref_menu, menu);

        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {

            case R.id.menu_add_keyserver:
                startAddKeyserverDialog();
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private void startAddKeyserverDialog() {
        // keyserver and position have no meaning
        startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.ADD, null, -1);
    }

    private void startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction action,
                                          String keyserver, final int position) {
        Handler returnHandler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                Bundle data = message.getData();
                switch (message.what) {
                    case AddEditKeyserverDialogFragment.MESSAGE_OKAY: {
                        boolean deleted =
                                data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER_DELETED
                                        , false);
                        if (deleted) {
                            Notify.create(getActivity(),
                                    getActivity().getString(
                                            R.string.keyserver_preference_deleted, mKeyservers.get(position)),
                                    Notify.Style.OK)
                                    .show();
                            deleteKeyserver(position);
                            return;
                        }
                        boolean verified =
                                data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_VERIFIED);
                        if (verified) {
                            Notify.create(getActivity(),
                                    R.string.add_keyserver_connection_verified, Notify.Style.OK).show();
                        } else {
                            Notify.create(getActivity(),
                                    R.string.add_keyserver_without_verification,
                                    Notify.Style.WARN).show();
                        }
                        String keyserver = data.getString(
                                AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER);

                        AddEditKeyserverDialogFragment.DialogAction dialogAction
                                = (AddEditKeyserverDialogFragment.DialogAction) data.getSerializable(
                                AddEditKeyserverDialogFragment.MESSAGE_DIALOG_ACTION);
                        switch (dialogAction) {
                            case ADD:
                                addKeyserver(keyserver);
                                break;
                            case EDIT:
                                editKeyserver(keyserver, position);
                                break;
                        }
                        break;
                    }
                }
            }
        };

        // Create a new Messenger for the communication back
        Messenger messenger = new Messenger(returnHandler);
        AddEditKeyserverDialogFragment dialogFragment = AddEditKeyserverDialogFragment
                .newInstance(messenger, action, keyserver, position);
        dialogFragment.show(getFragmentManager(), "addKeyserverDialog");
    }

    private void addKeyserver(String keyserver) {
        mKeyservers.add(keyserver);
        mAdapter.notifyItemInserted(mKeyservers.size() - 1);
        saveKeyserverList();
    }

    private void editKeyserver(String newKeyserver, int position) {
        mKeyservers.set(position, newKeyserver);
        mAdapter.notifyItemChanged(position);
        saveKeyserverList();
    }

    private void deleteKeyserver(int position) {
        if (mKeyservers.size() == 1) {
            Notify.create(getActivity(), R.string.keyserver_preference_cannot_delete_last,
                    Notify.Style.ERROR).show();
            return;
        }
        mKeyservers.remove(position);
        // we use this
        mAdapter.notifyItemRemoved(position);
        if (position == 0 && mKeyservers.size() > 0) {
            // if we deleted the first item, we need the adapter to redraw the new first item
            mAdapter.notifyItemChanged(0);
        }
        saveKeyserverList();
    }

    private void saveKeyserverList() {
        String servers[] = mKeyservers.toArray(new String[mKeyservers.size()]);
        Preferences.getPreferences(getActivity()).setKeyServers(servers);
    }

    @Override
    public void onItemClick(View view, int position) {
        startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.EDIT,
                mKeyservers.get(position), position);
    }

    public class KeyserverListAdapter extends RecyclerView.Adapter<KeyserverListAdapter.ViewHolder>
            implements ItemTouchHelperAdapter {

        private final List<String> mKeyservers;

        public KeyserverListAdapter(List<String> keyservers) {
            mKeyservers = keyservers;
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.settings_keyserver_item, parent, false);
            return new ViewHolder(view);
        }

        @Override
        public void onBindViewHolder(final ViewHolder holder, int position) {
            holder.keyserverUrl.setText(mKeyservers.get(position));

            // Start a drag whenever the handle view it touched
            holder.dragHandleView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
                        mItemTouchHelper.startDrag(holder);
                    }
                    return false;
                }
            });

            selectUnselectKeyserver(holder, position);
        }

        private void selectUnselectKeyserver(ViewHolder holder, int position) {

            if (position == 0) {
                holder.showAsSelectedKeyserver();
            } else {
                holder.showAsUnselectedKeyserver();
            }
        }

        @Override
        public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target,
                               int fromPosition, int toPosition) {
            Collections.swap(mKeyservers, fromPosition, toPosition);
            saveKeyserverList();
            selectUnselectKeyserver((ViewHolder) target, fromPosition);
            // we don't want source to change color while dragging, therefore we just set
            // isSelectedKeyserver instead of selectUnselectKeyserver
            ((ViewHolder) source).isSelectedKeyserver = toPosition == 0;

            notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public int getItemCount() {
            return mKeyservers.size();
        }

        public class ViewHolder extends RecyclerView.ViewHolder implements
                ItemTouchHelperViewHolder {

            public final ViewGroup outerLayout;
            public final TextView selectedServerLabel;
            public final TextView keyserverUrl;
            public final ImageView dragHandleView;

            private boolean isSelectedKeyserver = false;

            public ViewHolder(View itemView) {
                super(itemView);
                outerLayout = (ViewGroup) itemView.findViewById(R.id.outer_layout);
                selectedServerLabel = (TextView) itemView.findViewById(
                        R.id.selected_keyserver_title);
                keyserverUrl = (TextView) itemView.findViewById(R.id.keyserver_tv);
                dragHandleView = (ImageView) itemView.findViewById(R.id.drag_handle);

                itemView.setClickable(true);
            }

            public void showAsSelectedKeyserver() {
                isSelectedKeyserver = true;
                selectedServerLabel.setVisibility(View.VISIBLE);
                outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark));
            }

            public void showAsUnselectedKeyserver() {
                isSelectedKeyserver = false;
                selectedServerLabel.setVisibility(View.GONE);
                outerLayout.setBackgroundColor(Color.WHITE);
            }

            @Override
            public void onItemSelected() {
                selectedServerLabel.setVisibility(View.GONE);
                itemView.setBackgroundColor(Color.LTGRAY);
            }

            @Override
            public void onItemClear() {
                if (isSelectedKeyserver) {
                    showAsSelectedKeyserver();
                } else {
                    showAsUnselectedKeyserver();
                }
            }
        }
    }
}
lass="p">; #if GWIN_NEED_FLASHING static GTimer FlashTimer; #endif #if !GWIN_REDRAW_IMMEDIATE static GTimer RedrawTimer; static void RedrawTimerFn(void *param); #endif static volatile uint8_t RedrawPending; #define DOREDRAW_INVISIBLES 0x01 #define DOREDRAW_VISIBLES 0x02 #define DOREDRAW_FLASHRUNNING 0x04 /*----------------------------------------------- * Window Routines *-----------------------------------------------*/ void _gwmInit(void) { gfxSemInit(&gwinsem, 1, 1); gfxQueueASyncInit(&_GWINList); #if GWIN_NEED_FLASHING gtimerInit(&FlashTimer); #endif #if !GWIN_REDRAW_IMMEDIATE gtimerInit(&RedrawTimer); gtimerStart(&RedrawTimer, RedrawTimerFn, 0, gTrue, gDelayForever); #endif _GWINwm = (GWindowManager *)&GNullWindowManager; _GWINwm->vmt->Init(); } void _gwmDeinit(void) { GHandle gh; while((gh = gwinGetNextWindow(0))) gwinDestroy(gh); _GWINwm->vmt->DeInit(); #if !GWIN_REDRAW_IMMEDIATE gtimerDeinit(&RedrawTimer); #endif gfxQueueASyncDeinit(&_GWINList); gfxSemDestroy(&gwinsem); } #if GWIN_REDRAW_IMMEDIATE #define TriggerRedraw(void) _gwinFlushRedraws(REDRAW_NOWAIT); #else #define TriggerRedraw() gtimerJab(&RedrawTimer); static void RedrawTimerFn(void *param) { (void) param; _gwinFlushRedraws(REDRAW_NOWAIT); } #endif void _gwinFlushRedraws(GRedrawMethod how) { GHandle gh; // Do we really need to do anything? if (!RedrawPending) return; // Obtain the drawing lock if (how == REDRAW_WAIT) gfxSemWait(&gwinsem, gDelayForever); else if (how == REDRAW_NOWAIT && !gfxSemWait(&gwinsem, gDelayNone)) // Someone is drawing - They will do the redraw when they are finished return; // Do loss of visibility first while ((RedrawPending & DOREDRAW_INVISIBLES)) { RedrawPending &= ~DOREDRAW_INVISIBLES; // Catch new requests for(gh = gwinGetNextWindow(0); gh; gh = gwinGetNextWindow(gh)) { if ((gh->flags & (GWIN_FLG_NEEDREDRAW|GWIN_FLG_SYSVISIBLE)) != GWIN_FLG_NEEDREDRAW) continue; // Do the redraw #if GDISP_NEED_CLIP gdispGSetClip(gh->display, gh->x, gh->y, gh->width, gh->height); _GWINwm->vmt->Redraw(gh); gdispGUnsetClip(gh->display); #else _GWINwm->vmt->Redraw(gh); #endif // Postpone further redraws #if !GWIN_REDRAW_IMMEDIATE && !GWIN_REDRAW_SINGLEOP if (how == REDRAW_NOWAIT) { RedrawPending |= DOREDRAW_INVISIBLES; TriggerRedraw(); goto releaselock; } #endif } } // Do the visible windows next while ((RedrawPending & DOREDRAW_VISIBLES)) { RedrawPending &= ~DOREDRAW_VISIBLES; // Catch new requests for(gh = gwinGetNextWindow(0); gh; gh = gwinGetNextWindow(gh)) { if ((gh->flags & (GWIN_FLG_NEEDREDRAW|GWIN_FLG_SYSVISIBLE)) != (GWIN_FLG_NEEDREDRAW|GWIN_FLG_SYSVISIBLE)) continue; // Do the redraw #if GDISP_NEED_CLIP gdispGSetClip(gh->display, gh->x, gh->y, gh->width, gh->height); _GWINwm->vmt->Redraw(gh); gdispGUnsetClip(gh->display); #else _GWINwm->vmt->Redraw(gh); #endif // Postpone further redraws (if there are any and the options are set right) #if !GWIN_REDRAW_IMMEDIATE && !GWIN_REDRAW_SINGLEOP if (how == REDRAW_NOWAIT) { while((gh = gwinGetNextWindow(gh))) { if ((gh->flags & (GWIN_FLG_NEEDREDRAW|GWIN_FLG_SYSVISIBLE)) == (GWIN_FLG_NEEDREDRAW|GWIN_FLG_SYSVISIBLE)) { RedrawPending |= DOREDRAW_VISIBLES; TriggerRedraw(); break; } } goto releaselock; } #endif } } #if !GWIN_REDRAW_IMMEDIATE && !GWIN_REDRAW_SINGLEOP releaselock: #endif // Release the lock if (how == REDRAW_WAIT || how == REDRAW_NOWAIT) gfxSemSignal(&gwinsem); } void _gwinUpdate(GHandle gh) { // Only redraw if visible if (!(gh->flags & GWIN_FLG_SYSVISIBLE)) return; // Mark for redraw gh->flags |= GWIN_FLG_NEEDREDRAW; RedrawPending |= DOREDRAW_VISIBLES; // Asynchronous redraw TriggerRedraw(); } #if GWIN_NEED_CONTAINERS void _gwinRippleVisibility(void) { GHandle gh; // Check each window's visibility is consistent with its parents for(gh = gwinGetNextWindow(0); gh; gh = gwinGetNextWindow(gh)) { switch(gh->flags & (GWIN_FLG_SYSVISIBLE|GWIN_FLG_VISIBLE)) { case GWIN_FLG_VISIBLE: if (!gh->parent || (gh->parent->flags & GWIN_FLG_SYSVISIBLE)) { // We have been made visible gh->flags |= (GWIN_FLG_SYSVISIBLE|GWIN_FLG_NEEDREDRAW|GWIN_FLG_BGREDRAW); // Do we want to grab the focus _gwinFixFocus(gh); RedrawPending |= DOREDRAW_VISIBLES; } break; case (GWIN_FLG_VISIBLE|GWIN_FLG_SYSVISIBLE): if (!gh->parent || (gh->parent->flags & GWIN_FLG_SYSVISIBLE)) break; // Parent has been made invisible gh->flags &= ~GWIN_FLG_SYSVISIBLE; // No focus for us anymore _gwinFixFocus(gh); break; case GWIN_FLG_SYSVISIBLE: // We have been made invisible gh->flags &= ~GWIN_FLG_SYSVISIBLE; if (!gh->parent || (gh->parent->flags & GWIN_FLG_SYSVISIBLE)) { // The parent is visible so we must clear the area we took gh->flags |= (GWIN_FLG_NEEDREDRAW|GWIN_FLG_BGREDRAW); // No focus for us anymore _gwinFixFocus(gh); RedrawPending |= DOREDRAW_INVISIBLES; } break; } } } #endif gBool _gwinDrawStart(GHandle gh) { // This test should occur inside the lock. We do this // here as well as an early out (more efficient). if (!(gh->flags & GWIN_FLG_SYSVISIBLE)) return gFalse; // Obtain the drawing lock gfxSemWait(&gwinsem, gDelayForever); // Re-test visibility as we may have waited a while if (!(gh->flags & GWIN_FLG_SYSVISIBLE)) { _gwinDrawEnd(gh); return gFalse; } // OK - we are ready to draw. #if GDISP_NEED_CLIP gdispGSetClip(gh->display, gh->x, gh->y, gh->width, gh->height); #endif return gTrue; } void _gwinDrawEnd(GHandle gh) { // Ensure there is no clip set #if GDISP_NEED_CLIP gdispGUnsetClip(gh->display); #endif // Look for something to redraw _gwinFlushRedraws(REDRAW_INSESSION); // Release the lock gfxSemSignal(&gwinsem); } gBool _gwinWMAdd(GHandle gh, const GWindowInit *pInit) { #if GWIN_NEED_CONTAINERS // Save the parent gh->parent = pInit->parent; // Ensure the display is consistent with any parents if (gh->parent && (!(gh->parent->flags & GWIN_FLG_CONTAINER) || gh->display != gh->parent->display)) return gFalse; #endif // Add to the window manager if (!_GWINwm->vmt->Add(gh, pInit)) return gFalse; #if GWIN_NEED_CONTAINERS // Notify the parent it has been added if (gh->parent && ((gcontainerVMT *)gh->parent->vmt)->NotifyAdd) ((gcontainerVMT *)gh->parent->vmt)->NotifyAdd(gh->parent, gh); #endif return gTrue; } void gwinSetWindowManager(struct GWindowManager *gwm) { if (!gwm) gwm = (GWindowManager *)&GNullWindowManager; if (_GWINwm != gwm) { _GWINwm->vmt->DeInit(); _GWINwm = gwm; _GWINwm->vmt->Init(); } } void gwinRedraw(GHandle gh) { // Only redraw if visible if (!(gh->flags & GWIN_FLG_SYSVISIBLE)) return; // Mark for redraw gh->flags |= GWIN_FLG_NEEDREDRAW; RedrawPending |= DOREDRAW_VISIBLES; // Synchronous redraw _gwinFlushRedraws(REDRAW_WAIT); } #if GWIN_NEED_CONTAINERS void gwinSetVisible(GHandle gh, gBool visible) { if (visible) { // Mark us as visible gh->flags |= GWIN_FLG_VISIBLE; } else { // Mark us as not visible gh->flags &= ~GWIN_FLG_VISIBLE; } // Fix everything up _gwinRippleVisibility(); if (RedrawPending) TriggerRedraw(); } #else void gwinSetVisible(GHandle gh, gBool visible) { if (visible) { if (!(gh->flags & GWIN_FLG_VISIBLE)) { gh->flags |= (GWIN_FLG_VISIBLE|GWIN_FLG_SYSVISIBLE|GWIN_FLG_NEEDREDRAW|GWIN_FLG_BGREDRAW); // Do we want to grab the focus _gwinFixFocus(gh); RedrawPending |= DOREDRAW_VISIBLES; TriggerRedraw(); } } else { if ((gh->flags & GWIN_FLG_VISIBLE)) { gh->flags &= ~(GWIN_FLG_VISIBLE|GWIN_FLG_SYSVISIBLE); gh->flags |= (GWIN_FLG_NEEDREDRAW|GWIN_FLG_BGREDRAW); // No focus for us anymore _gwinFixFocus(gh); RedrawPending |= DOREDRAW_INVISIBLES; TriggerRedraw(); } } } #endif #if GWIN_NEED_CONTAINERS // These two sub-functions set/clear system enable recursively. void gwinSetEnabled(GHandle gh, gBool enabled) { if (enabled) { // Mark us as enabled gh->flags |= GWIN_FLG_ENABLED; // Do we change our real enabled state if (!(gh->flags & GWIN_FLG_SYSENABLED) && (!gh->parent || (gh->parent->flags & GWIN_FLG_SYSENABLED))) { // Check each window's enabled state is consistent with its parents for(gh = gwinGetNextWindow(0); gh; gh = gwinGetNextWindow(gh)) { if ((gh->flags & (GWIN_FLG_SYSENABLED|GWIN_FLG_ENABLED)) == GWIN_FLG_ENABLED && (!gh->parent || (gh->parent->flags & GWIN_FLG_SYSENABLED))) { gh->flags |= GWIN_FLG_SYSENABLED; // Fix it // Do we want to grab the focus _gwinFixFocus(gh); _gwinUpdate(gh); } } } } else { gh->flags &= ~GWIN_FLG_ENABLED; // Do we need to change our real enabled state if ((gh->flags & GWIN_FLG_SYSENABLED)) { // Check each window's visibility is consistent with its parents for(gh = gwinGetNextWindow(0); gh; gh = gwinGetNextWindow(gh)) { if ((gh->flags & GWIN_FLG_SYSENABLED) && (!(gh->flags & GWIN_FLG_ENABLED) || (gh->parent && !(gh->parent->flags & GWIN_FLG_SYSENABLED)))) { gh->flags &= ~GWIN_FLG_SYSENABLED; // Fix it // No focus for us anymore _gwinFixFocus(gh); _gwinUpdate(gh); } } } } } #else void gwinSetEnabled(GHandle gh, gBool enabled) { if (enabled) { if (!(gh->flags & GWIN_FLG_ENABLED)) { gh->flags |= (GWIN_FLG_ENABLED|GWIN_FLG_SYSENABLED); // Do we want to grab the focus _gwinFixFocus(gh); _gwinUpdate(gh); } } else { if ((gh->flags & GWIN_FLG_ENABLED)) { gh->flags &= ~(GWIN_FLG_ENABLED|GWIN_FLG_SYSENABLED); // No focus for us anymore _gwinFixFocus(gh); _gwinUpdate(gh); } } } #endif void gwinMove(GHandle gh, gCoord x, gCoord y) { _GWINwm->vmt->Move(gh, x, y); } void gwinResize(GHandle gh, gCoord width, gCoord height) { _GWINwm->vmt->Size(gh, width, height); } void gwinSetMinMax(GHandle gh, GWindowMinMax minmax) { _GWINwm->vmt->MinMax(gh, minmax); } void gwinRaise(GHandle gh) { _GWINwm->vmt->Raise(gh); } GWindowMinMax gwinGetMinMax(GHandle gh) { if (gh->flags & GWIN_FLG_MINIMIZED) return GWIN_MINIMIZE; if (gh->flags & GWIN_FLG_MAXIMIZED) return GWIN_MAXIMIZE; return GWIN_NORMAL; } void gwinRedrawDisplay(GDisplay *g, gBool preserve) { GHandle gh; for(gh = gwinGetNextWindow(0); gh; gh = gwinGetNextWindow(gh)) { // Skip if it is for a different display if (g && gh->display != g) continue; #if GWIN_NEED_CONTAINERS // Skip if it is not a top level window (parents internally take care of their children) if (gh->parent) continue; #endif // Only visible windows are to be redrawn if (!(gh->flags & GWIN_FLG_SYSVISIBLE)) continue; if (!preserve) gh->flags |= GWIN_FLG_BGREDRAW; _gwinUpdate(gh); } } GHandle gwinGetNextWindow(GHandle gh) { return gh ? (GHandle)gfxQueueASyncNext(&gh->wmq) : (GHandle)gfxQueueASyncPeek(&_GWINList); } #if GWIN_NEED_FLASHING static void FlashTimerFn(void *param) { GHandle gh; (void) param; // Assume we will be stopping RedrawPending &= ~DOREDRAW_FLASHRUNNING; // Swap the flash state _gwinFlashState = !_gwinFlashState; // Redraw all flashing windows for(gh = (GHandle)gfxQueueASyncPeek(&_GWINList); gh; gh = (GHandle)gfxQueueASyncNext(&gh->wmq)) { if ((gh->flags & GWIN_FLG_FLASHING)) { RedrawPending |= DOREDRAW_FLASHRUNNING; _gwinUpdate(gh); } } // Do we have no flashers left? if (!(RedrawPending & DOREDRAW_FLASHRUNNING)) gtimerStop(&FlashTimer); } void gwinSetFlashing(GHandle gh, gBool flash) { // Start flashing? if (flash) { gh->flags |= GWIN_FLG_FLASHING; // A redraw will occur on the next flash period. // Start the flash timer if needed if (!(RedrawPending & DOREDRAW_FLASHRUNNING)) { RedrawPending |= DOREDRAW_FLASHRUNNING; // Ensure we start the timer with flash bit on _gwinFlashState = gFalse; FlashTimerFn(0); // First flash gtimerStart(&FlashTimer, FlashTimerFn, 0, gTrue, GWIN_FLASHING_PERIOD); // Subsequent flashes } // Stop flashing? } else if ((gh->flags & GWIN_FLG_FLASHING)) { gh->flags &= ~GWIN_FLG_FLASHING; // We need to manually redraw as the timer is now turned off for this window _gwinUpdate(gh); } } #if GWIN_NEED_WIDGET const GColorSet *_gwinGetFlashedColor(GWidgetObject *gw, const GColorSet *pcol, gBool flashOffState) { // Does the flashing state affect the current colors? if ((gw->g.flags & GWIN_FLG_FLASHING) && _gwinFlashState) { // For a pressed state show an unpressed state if (pcol == &gw->pstyle->pressed) pcol = &gw->pstyle->enabled; // For a non-pressed state (if allowed) show a pressed state else if (flashOffState && pcol == &gw->pstyle->enabled) pcol = &gw->pstyle->pressed; } return pcol; } #endif #endif /*----------------------------------------------- * "Null" Window Manager Routines *-----------------------------------------------*/ // This is a parent reveal operation #define GWIN_FLG_PARENTREVEAL (GWIN_FIRST_WM_FLAG << 0) // Minimum dimensions #define MIN_WIN_WIDTH 3 #define MIN_WIN_HEIGHT 3 static void WM_Init(void); static void WM_DeInit(void); static gBool WM_Add(GHandle gh, const GWindowInit *pInit); static void WM_Delete(GHandle gh); static void WM_Redraw(GHandle gh); static void WM_Size(GHandle gh, gCoord w, gCoord h); static void WM_Move(GHandle gh, gCoord x, gCoord y); static void WM_Raise(GHandle gh); static void WM_MinMax(GHandle gh, GWindowMinMax minmax); static const gwmVMT GNullWindowManagerVMT = { WM_Init, WM_DeInit, WM_Add, WM_Delete, WM_Redraw, WM_Size, WM_Move, WM_Raise, WM_MinMax, }; const GWindowManager GNullWindowManager = { &GNullWindowManagerVMT, }; static void WM_Init(void) { // We don't need to do anything here. // A full window manager would move the windows around, add borders etc // clear the screen // cycle through the windows already defined displaying them // or cut all the window areas out of the screen and clear the remainder } static void WM_DeInit(void) { // We don't need to do anything here. // A full window manager would remove any borders etc } static gBool WM_Add(GHandle gh, const GWindowInit *pInit) { // Note the window will not currently be marked as visible // Put it on the end of the queue gfxQueueASyncPut(&_GWINList, &gh->wmq); // Make sure the size/position is valid - prefer position over size. gh->width = MIN_WIN_WIDTH; gh->height = MIN_WIN_HEIGHT; gh->x = gh->y = 0; WM_Move(gh, pInit->x, pInit->y); WM_Size(gh, pInit->width, pInit->height); return gTrue; } static void WM_Delete(GHandle gh) { // Remove it from the window list gfxSemWait(&gwinsem, gDelayForever); gfxQueueASyncRemove(&_GWINList, &gh->wmq); gfxSemSignal(&gwinsem); } static void WM_Redraw(GHandle gh) { uint32_t flags; flags = gh->flags; gh->flags &= ~(GWIN_FLG_NEEDREDRAW|GWIN_FLG_BGREDRAW|GWIN_FLG_PARENTREVEAL); #if GWIN_NEED_CONTAINERS redo_redraw: #endif if ((flags & GWIN_FLG_SYSVISIBLE)) { if (gh->vmt->Redraw) gh->vmt->Redraw(gh); else if ((flags & GWIN_FLG_BGREDRAW)) { // We can't redraw but we want full coverage so just clear the area gdispGFillArea(gh->display, gh->x, gh->y, gh->width, gh->height, gh->bgcolor); // Only do an after clear if this is not a parent reveal if (!(flags & GWIN_FLG_PARENTREVEAL) && gh->vmt->AfterClear) gh->vmt->AfterClear(gh); } #if GWIN_NEED_CONTAINERS // If this is container but not a parent reveal, mark any visible children for redraw // We redraw our children here as we have overwritten them in redrawing the parent // as GDISP/GWIN doesn't support complex clipping regions. if ((flags & (GWIN_FLG_CONTAINER|GWIN_FLG_PARENTREVEAL)) == GWIN_FLG_CONTAINER) { // Container redraw is done for(gh = gwinGetFirstChild(gh); gh; gh = gwinGetSibling(gh)) _gwinUpdate(gh); return; } #endif } else { if ((flags & GWIN_FLG_BGREDRAW)) { GHandle gx; #if GWIN_NEED_CONTAINERS if (gh->parent) { // Child redraw is done // Get the parent to redraw the area gh = gh->parent; // The parent is already marked for redraw - don't do it now. if ((gh->flags & GWIN_FLG_NEEDREDRAW)) return; // Use the existing clipping region and redraw now gh->flags |= (GWIN_FLG_BGREDRAW|GWIN_FLG_PARENTREVEAL); goto redo_redraw; } #endif // Clear the area to the background color gdispGFillArea(gh->display, gh->x, gh->y, gh->width, gh->height, gwinGetDefaultBgColor()); // Now loop over all windows looking for overlaps. Redraw them if they overlap the newly exposed area. for(gx = gwinGetNextWindow(0); gx; gx = gwinGetNextWindow(gx)) { if ((gx->flags & GWIN_FLG_SYSVISIBLE) && gx->display == gh->display && gx->x < gh->x+gh->width && gx->y < gh->y+gh->height && gx->x+gx->width >= gh->x && gx->y+gx->height >= gh->y) { if (gx->vmt->Redraw) gx->vmt->Redraw(gx); else // We can't redraw this window but we want full coverage so just clear the area gdispGFillArea(gx->display, gx->x, gx->y, gx->width, gx->height, gx->bgcolor); } } } } } static void WM_Size(GHandle gh, gCoord w, gCoord h) { gCoord v; #if GWIN_NEED_CONTAINERS if (gh->parent) { // Clip to the container v = gh->parent->x + gh->parent->width - ((const gcontainerVMT *)gh->parent->vmt)->RightBorder(gh->parent); if (gh->x+w > v) w = v - gh->x; v = gh->parent->y + gh->parent->height - ((const gcontainerVMT *)gh->parent->vmt)->BottomBorder(gh->parent); if (gh->y+h > v) h = v - gh->y; } #endif // Clip to the screen v = gdispGGetWidth(gh->display); if (gh->x+w > v) w = v - gh->x; v = gdispGGetHeight(gh->display); if (gh->y+h > v) h = v - gh->y; // Give it a minimum size if (w < MIN_WIN_WIDTH) w = MIN_WIN_WIDTH; if (h < MIN_WIN_HEIGHT) h = MIN_WIN_HEIGHT; // If there has been no resize just exit if (gh->width == w && gh->height == h) return; // Set the new size and redraw if ((gh->flags & GWIN_FLG_SYSVISIBLE)) { if (w >= gh->width && h >= gh->height) { // The new size is larger - just redraw gh->width = w; gh->height = h; _gwinUpdate(gh); } else { // We need to make this window invisible and ensure that has been drawn gwinSetVisible(gh, gFalse); _gwinFlushRedraws(REDRAW_WAIT); // Resize gh->width = w; gh->height = h; #if GWIN_NEED_CONTAINERS // Any children outside the new area need to be moved if ((gh->flags & GWIN_FLG_CONTAINER)) { GHandle child; // Move to their old relative location. THe WM_Move() will adjust as necessary for(child = gwinGetFirstChild(gh); child; child = gwinGetSibling(child)) WM_Move(child, child->x-gh->x-((const gcontainerVMT *)gh->vmt)->LeftBorder(gh), child->y-gh->y-((const gcontainerVMT *)gh->vmt)->TopBorder(gh)); } #endif // Mark it visible again in its new location gwinSetVisible(gh, gTrue); } } else { gh->width = w; gh->height = h; #if GWIN_NEED_CONTAINERS // Any children outside the new area need to be moved if ((gh->flags & GWIN_FLG_CONTAINER)) { GHandle child; // Move to their old relative location. THe WM_Move() will adjust as necessary for(child = gwinGetFirstChild(gh); child; child = gwinGetSibling(child)) WM_Move(child, child->x-gh->x-((const gcontainerVMT *)gh->vmt)->LeftBorder(gh), child->y-gh->y-((const gcontainerVMT *)gh->vmt)->TopBorder(gh)); } #endif } } static void WM_Move(GHandle gh, gCoord x, gCoord y) { gCoord u, v; #if GWIN_NEED_CONTAINERS if (gh->parent) { // Clip to the parent size u = gh->parent->width - ((const gcontainerVMT *)gh->parent->vmt)->LeftBorder(gh->parent) - ((const gcontainerVMT *)gh->parent->vmt)->RightBorder(gh->parent); v = gh->parent->height - ((const gcontainerVMT *)gh->parent->vmt)->TopBorder(gh->parent) - ((const gcontainerVMT *)gh->parent->vmt)->BottomBorder(gh->parent); } else #endif { // Clip to the screen u = gdispGGetWidth(gh->display); v = gdispGGetHeight(gh->display); } // Make sure we are positioned in the appropriate area if (x+gh->width > u) x = u-gh->width; if (x < 0) x = 0; if (y+gh->height > v) y = v-gh->height; if (y < 0) y = 0; // Make sure we don't overflow the appropriate area u -= x; v -= y; if (gh->width < u) u = gh->width; if (gh->height < v) v = gh->height; if (u != gh->width || v != gh->height) WM_Size(gh, u, v); #if GWIN_NEED_CONTAINERS if (gh->parent) { // Convert to a screen relative position x += gh->parent->x + ((const gcontainerVMT *)gh->parent->vmt)->LeftBorder(gh->parent); y += gh->parent->y + ((const gcontainerVMT *)gh->parent->vmt)->TopBorder(gh->parent); } #endif // If there has been no move just exit if (gh->x == x && gh->y == y) return; // Clear the old area and then redraw if ((gh->flags & GWIN_FLG_SYSVISIBLE)) { // We need to make this window invisible and ensure that has been drawn gwinSetVisible(gh, gFalse); _gwinFlushRedraws(REDRAW_WAIT); // Do the move u = gh->x; gh->x = x; v = gh->y; gh->y = y; #if GWIN_NEED_CONTAINERS // Any children need to be moved if ((gh->flags & GWIN_FLG_CONTAINER)) { GHandle child; // Move to their old relative location. THe WM_Move() will adjust as necessary for(child = gwinGetFirstChild(gh); child; child = gwinGetSibling(child)) WM_Move(child, child->x-u-((const gcontainerVMT *)gh->vmt)->LeftBorder(gh), child->y-v-((const gcontainerVMT *)gh->vmt)->TopBorder(gh)); } #endif gwinSetVisible(gh, gTrue); } else { u = gh->x; gh->x = x; v = gh->y; gh->y = y; #if GWIN_NEED_CONTAINERS // Any children need to be moved if ((gh->flags & GWIN_FLG_CONTAINER)) { GHandle child; // Move to their old relative location. THe WM_Move() will adjust as necessary for(child = gwinGetFirstChild(gh); child; child = gwinGetSibling(child)) WM_Move(child, child->x-u-((const gcontainerVMT *)gh->vmt)->LeftBorder(gh), child->y-v-((const gcontainerVMT *)gh->vmt)->TopBorder(gh)); } #endif } } static void WM_MinMax(GHandle gh, GWindowMinMax minmax) { (void)gh; (void) minmax; // We don't support minimising, maximising or restoring } static void WM_Raise(GHandle gh) { // Take it off the list and then put it back on top // The order of the list then reflects the z-order. gfxSemWait(&gwinsem, gDelayForever); gfxQueueASyncRemove(&_GWINList, &gh->wmq); gfxQueueASyncPut(&_GWINList, &gh->wmq); #if GWIN_NEED_CONTAINERS // Any children need to be raised too if ((gh->flags & GWIN_FLG_CONTAINER)) { GHandle gx; GHandle child; gBool restart; // Raise the children too // Note: Children can also have their own children so after each move we have to start again. for (gx = gwinGetNextWindow(0); gx; gx = gwinGetNextWindow(gx)) { if ((gx->flags & GWIN_FLG_CONTAINER)) { restart = gFalse; for (child = gwinGetNextWindow(0); child && child != gx; child = gwinGetNextWindow(child)) { if (child->parent == gx) { // Oops - this child is behind its parent. Move it to the front. gfxQueueASyncRemove(&_GWINList, &child->wmq); gfxQueueASyncPut(&_GWINList, &child->wmq); // Restart at the front of the list for this parent container as we have moved this child // to the end of the list. We also need to restart everything once this container is done. child = 0; restart = gTrue; } } if (restart) gx = 0; } } } #endif gfxSemSignal(&gwinsem); // Redraw the window _gwinUpdate(gh); } #endif /* GFX_USE_GWIN && GWIN_NEED_WINDOWMANAGER */ /** @} */